Share via


Device Provisioning Service (DPS) を使用して Azure Digital Twins でデバイスを自動管理する

この記事では、Azure Digital Twins と Device Provisioning Service (DPS) を統合する方法について説明します。

この記事で説明されているソリューションを使用すると、Device Provisioning Service を使用して、Azure Digital Twins で IoT Hub デバイスを "プロビジョニング" および "廃止" するプロセスを自動化できます。

"プロビジョニング" および "廃止" ステージの詳細と、すべてのエンタープライズ IoT プロジェクトに共通する一般的なデバイス管理ステージの詳細については、IoT Hub のデバイス管理ドキュメントで「デバイスのライフサイクル」のセクションを参照してください。

前提条件

プロビジョニングを設定する前に、次のリソースを設定する必要があります。

  • Azure Digital Twins インスタンス。 インスタンスと認証の設定に関する記事の手順に従って、Azure Digital Twins のインスタンスを作成します。 Azure portal (手順) でインスタンスのホスト名を収集します。
  • IoT Hub。 手順については、「IoT Hub のクイックスタート」の「IoT Hub の作成」セクションを参照してください。
  • IoT Hub のデータに基づいてデジタル ツイン情報を更新する Azure 関数IoT ハブ データの取り込みに関する記事の手順に従って、この Azure 関数を作成します。 この記事で使用する関数 を収集します。

このサンプルでは、Device Provisioning Service を使用したプロビジョニングを含むデバイス シミュレーターも使用します。 デバイス シミュレーターは、Azure Digital Twins と IoT Hub の統合のサンプルにあります。 サンプル用の GitHub リポジトリに移動し、マシンにサンプル プロジェクトを取得します。[Code]\(コード\) ボタンと、[Download ZIP]\(ZIP のダウンロード\) を選択することによって、.ZIP ファイルとしてダウンロードできます。

Screenshot of the digital-twins-iothub-integration repo on GitHub, highlighting the steps to download it as a zip.

ダウンロードしたフォルダーを解凍します。

お使いのコンピューターに Node.js がインストールされている必要があります。 デバイス シミュレーターは Node.js バージョン 10.0.x 以降に基づいています。

ソリューションのアーキテクチャ

このソリューションには、デバイス プロビジョニング サービスを使用して、Azure Digital Twins でデバイスをプロビジョニングおよび廃止する手順が含まれています。

ソリューションにデバイスを割り当てるためには、サーモスタット デバイスと DPS の間でデータが流れます。 その後、データは DPS から IoT Hub に流れ、Azure 関数を介して Azure Digital Twins に流れます。

デバイスを廃止するためには、手動削除のデバイスからのデータが、IoT Hub、Event Hubs、Azure 関数を介して Azure Digital Twins に流れます。

次の図は、このアーキテクチャを示しています。

Diagram of device and several Azure services in an end-to-end scenario showing the data flow.

この記事は、それぞれこの完全なアーキテクチャの一部に焦点を当てた 2 つのセクションに分かれています。

Device Provisioning Service を使用してデバイスを自動プロビジョニングする

このセクションでは、Device Provisioning Service を Azure Digital Twins に接続し、以下のパスを使用してデバイスを自動プロビジョニングします。 この図は、先ほど示したアーキテクチャの全体図からの抜粋です。

Diagram of Provision flow—an excerpt of the solution architecture diagram following data from a thermostat into Azure Digital Twins.

プロセス フローの説明は次のとおりです。

  1. デバイスから DPS エンドポイントへの通信によって、同一性を証明する識別情報が渡されます。
  2. DPS によって、登録 ID とキーをデバイスの登録リストに照らして検証することでデバイスの同一性が検証され、割り当てを行う Azure 関数が呼び出されます。
  3. その Azure 関数により、Azure Digital Twins 内にデバイスの新しいツインが作成されます。 ツインは、デバイスの 登録 IDと同じ名前になります。
  4. DPS によってデバイスが IoT ハブに登録され、デバイスの選択されたツイン状態が設定されます。
  5. IoT ハブからデバイス ID 情報と IoT ハブの接続情報がデバイスに返されます。 これで、デバイスから IoT ハブに接続できるようになります。

以下のセクションでは、このデバイス自動プロビジョニング フローを設定する手順について説明します。

Device Provisioning Service の作成

デバイス プロビジョニング サービスを使用して新しいデバイスをプロビジョニングすると、登録 ID と同じ名前でそのデバイスの新しいツインがAzure Digital Twins 内に作成されます。

Device Provisioning Service のインスタンスを作成します。これが IoT デバイスのプロビジョニングに使用されます。 以下の Azure CLI の手順を使用するか、「Azure Portal で IoT Hub Device Provisioning Service を設定する」に従って Azure portal を使用することができます。

次の Azure CLI コマンドを実行すると、デバイス プロビジョニング サービスが作成されます。 デバイスプロビジョニングサービスの名前、リソースグループ、およびリージョンを指定する必要があります。 デバイス プロビジョニング サービスがサポートされているリージョンを確認するには、「リージョン別の利用可能な Azure 製品」ページを参照してください。 このコマンドは、Cloud Shell で実行するか、Azure CLI がマシンにインストールされている場合はローカルで実行できます。

az iot dps create --name <Device-Provisioning-Service-name> --resource-group <resource-group-name> --location <region>

デバイス プロビジョニング サービスで使用する関数を追加する

「前提条件」セクションで作成した関数アプリ プロジェクト内に、デバイス プロビジョニング サービスと共に使用する新しい関数を作成します。 この関数は、Device Provisioning Service によって、新しいデバイスをプロビジョニングするカスタム割り当てポリシー内で使用されます。

マシン上の関数アプリに移動し、以下の手順に従ってください。

  1. 最初に、関数アプリ プロジェクトに、種類が HTTP トリガーの新しい関数を作成します。

  2. 新しい NuGet パッケージをプロジェクトに追加します: Microsoft.Azure.Devices.Provisioning.Servic。 コード内で使用されているパッケージが既にプロジェクトに含まれていない場合は、さらにパッケージをプロジェクトに追加することが必要になる場合があります。

  3. 新しく作成された関数コード ファイルに次のコードを貼り付け、関数に DpsAdtAllocationFunc.cs という名前を指定し、ファイルを保存します。

    // Copyright (c) Microsoft. All rights reserved.
    // Licensed under the MIT license. See LICENSE file in the project root for full license information.
    
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Azure;
    using Azure.Core.Pipeline;
    using Azure.DigitalTwins.Core;
    using Azure.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.Azure.Devices.Shared;
    using Microsoft.Azure.Devices.Provisioning.Service;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    
    namespace Samples.AdtIothub
    {
        public static class DpsAdtAllocationFunc
        {
            private static string adtInstanceUrl = Environment.GetEnvironmentVariable("ADT_SERVICE_URL");
            private static readonly HttpClient singletonHttpClientInstance = new HttpClient();
    
            [FunctionName("DpsAdtAllocationFunc")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
            {
                // Get request body
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                log.LogDebug($"Request.Body: {requestBody}");
                dynamic data = JsonConvert.DeserializeObject(requestBody);
    
                // Get registration ID of the device
                string regId = data?.deviceRuntimeContext?.registrationId;
    
                bool fail = false;
                string message = "Uncaught error";
                var response = new ResponseObj();
    
                // Must have unique registration ID on DPS request
                if (regId == null)
                {
                    message = "Registration ID not provided for the device.";
                    log.LogInformation("Registration ID: NULL");
                    fail = true;
                }
                else
                {
                    string[] hubs = data?.linkedHubs.ToObject<string[]>();
    
                    // Must have hubs selected on the enrollment
                    if (hubs == null
                        || hubs.Length < 1)
                    {
                        message = "No hub group defined for the enrollment.";
                        log.LogInformation("linkedHubs: NULL");
                        fail = true;
                    }
                    else
                    {
                        // Find or create twin based on the provided registration ID and model ID
                        dynamic payloadContext = data?.deviceRuntimeContext?.payload;
                        string dtmi = payloadContext.modelId;
                        log.LogDebug($"payload.modelId: {dtmi}");
                        string dtId = await FindOrCreateTwinAsync(dtmi, regId, log);
    
                        // Get first linked hub (TODO: select one of the linked hubs based on policy)
                        response.iotHubHostName = hubs[0];
    
                        // Specify the initial tags for the device.
                        var tags = new TwinCollection();
                        tags["dtmi"] = dtmi;
                        tags["dtId"] = dtId;
    
                        // Specify the initial desired properties for the device.
                        var properties = new TwinCollection();
    
                        // Add the initial twin state to the response.
                        var twinState = new TwinState(tags, properties);
                        response.initialTwin = twinState;
                    }
                }
    
                log.LogDebug("Response: " + ((response.iotHubHostName != null)? JsonConvert.SerializeObject(response) : message));
    
                return fail
                    ? new BadRequestObjectResult(message)
                    : (ActionResult)new OkObjectResult(response);
            }
    
            public static async Task<string> FindOrCreateTwinAsync(string dtmi, string regId, ILogger log)
            {
                // Create Digital Twins client
                var cred = new DefaultAzureCredential();
                var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), cred);
    
                // Find existing DigitalTwin with registration ID
                try
                {
                    // Get DigitalTwin with Id 'regId'
                    BasicDigitalTwin existingDt = await client.GetDigitalTwinAsync<BasicDigitalTwin>(regId).ConfigureAwait(false);
    
                    // Check to make sure it is of the correct model type
                    if (StringComparer.OrdinalIgnoreCase.Equals(dtmi, existingDt.Metadata.ModelId))
                    {
                        log.LogInformation($"DigitalTwin {existingDt.Id} already exists");
                        return existingDt.Id;
                    }
    
                    // Found DigitalTwin but it is not of the correct model type
                    log.LogInformation($"Found DigitalTwin {existingDt.Id} but it is not of model {dtmi}");
                }
                catch(RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound)
                {
                    log.LogDebug($"Did not find DigitalTwin {regId}");
                }
    
                // Either the DigitalTwin was not found, or we found it but it is of a different model type
                // Create or replace it with what it needs to be, meaning if it was not found a brand new DigitalTwin will be created
                // and if it was of a different model, it will replace that existing DigitalTwin
                // If it was intended to only create the DigitalTwin if there is no matching DigitalTwin with the same Id,
                // ETag.All could have been used as the ifNonMatch parameter to the CreateOrReplaceDigitalTwinAsync method call.
                // Read more in the CreateOrReplaceDigitalTwinAsync documentation here:
                // https://docs.microsoft.com/en-us/dotnet/api/azure.digitaltwins.core.digitaltwinsclient.createorreplacedigitaltwinasync?view=azure-dotnet
                BasicDigitalTwin dt = await client.CreateOrReplaceDigitalTwinAsync(
                    regId, 
                    new BasicDigitalTwin
                    {
                        Metadata = { ModelId = dtmi },
                        Contents = 
                        {
                            { "Temperature", 0.0 }
                        }
                    }
                ).ConfigureAwait(false);
    
                log.LogInformation($"Digital Twin {dt.Id} created.");
                return dt.Id;
            }
        }
    
        /// <summary>
        /// Expected function result format
        /// </summary>
        public class ResponseObj
        {
            public string iotHubHostName { get; set; }
            public TwinState initialTwin { get; set; }
        }
    }
    
  4. DpsAdtAllocationFunc.cs 関数が含まれるプロジェクトを Azure の関数アプリに発行します。

    Visual Studio を使用して関数を発行する方法については、「Visual Studio を使用する Azure Functions の開発」を参照してください。 Visual Studio Code を使用して関数を発行する方法については、「Visual Studio Code を使用して Azure に C# 関数を作成する」を参照してください。 Azure CLI を使用して関数を発行する方法については、「Azure でコマンド ラインから C# 関数を作成する」を参照してください。

重要

前提条件」セクションで関数アプリを初めて作成する場合、その関数のアクセス ロールが既に割り当てられていて、Azure Digital Twins インスタンスにアクセスするためのアプリケーション設定が構成されている可能性があります。 これらは、関数アプリ全体に対して 1 回実行する必要があるため、続行する前にアプリで完了したことを確認してください。 手順については、"アプリ認証コードの記述" に関する記事の「発行されたアプリを構成する」セクションを参照してください。

デバイス プロビジョニング登録の作成

次に、カスタム割り当て関数を使用して Device Provisioning Service 内に登録を作成する必要があります。 登録を作成するには、Device Provisioning Service のドキュメントのカスタム割り当てポリシーに関する記事の「登録を作成する」セクションの手順のようにします。

このフローを実行するときは、次のオプションを選択して、作成した関数に登録をリンクします。

  • デバイスをハブに割り当てる方法を選択してください: カスタム (Azure 関数)。
  • このグループを割り当てることができる IoT ハブを選択します: IoT ハブの名前を選択するか、[新しい IoT ハブをリンク] ボタンを選択し、オプションから IoT ハブを選択します。

次に、[新しい関数の選択] ボタンをクリックして、関数アプリを登録グループにリンクします。 次に、以下の値を入力します。

  • サブスクリプション: お使いの Azure サブスクリプションに自動的にデータが設定されます。 適切なサブスクリプションであることを確認します。
  • 関数アプリ:ご利用の関数アプリの名前を選択します。
  • 関数: DpsAdtAllocationFunc を選択します。

詳細を保存してください。

Screenshot of the Customs enrollment group details window in the Azure portal.

登録を作成したら、それを選択して設定を表示します。 登録の主キーをコピーします。この記事では、後ほどこれを使用してデバイス シミュレーターを構成します。

デバイス シミュレーターの設定

このサンプルでは、Device Provisioning Service を使用したプロビジョニングを含むデバイス シミュレーターを使用します。 デバイス シミュレーターは、「前提条件」セクションでダウンロードしたAzure Digital Twins と IoT Hub の統合のサンプルにあります。

モデルのアップロード

デバイス シミュレーターは、この ID を持つモデルを使用するサーモスタットタイプのデバイスです。dtmi:contosocom:DigitalTwins:Thermostat;1 。 この種類のデバイスのツインを作成する前に、このモデルを Azure Digital Twins にアップロードする必要があります。

モデルは次のようになります。

{
    "@id": "dtmi:contosocom:DigitalTwins:Thermostat;1",
    "@type": "Interface",
    "@context": "dtmi:dtdl:context;3",
    "contents": [
      {
        "@type": "Property",
        "name": "Temperature",
        "schema": "double"
      }
    ]
  }

このモデルをツイン インスタンスにアップロードするには、次の Azure CLI コマンドを実行します。これにより、上記のモデルがインライン JSON としてアップロードされます。 このコマンドは、ご利用のブラウザーで Azure Cloud Shell から実行できます (Bash 環境を使用)。CLI がローカルにインストールされている場合は、ご利用のコンピューター上でも実行できます。 インスタンスのホスト名にはプレースホルダーが 1 つあります (インスタンスのフレンドリ名を使用することもできますが、パフォーマンスが若干低下します)。

az dt model create --dt-name <instance-hostname-or-name> --models '{  "@id": "dtmi:contosocom:DigitalTwins:Thermostat;1",  "@type": "Interface",  "@context": "dtmi:dtdl:context;2",  "contents": [    {      "@type": "Property",      "name": "Temperature",      "schema": "double"    }  ]}' 

Note

Bash 環境で Cloud Shell 以外のものを使用している場合は、インライン JSON で特定の文字をエスケープして、正しく解析されるようにする必要があります。 詳細については、さまざまなシェルでの特殊文字の使用に関するページを参照してください。

モデルの詳細については、モデルの管理に関する記事を参照してください。

シミュレーターを構成して実行する

ローカル マシンのコマンド ウィンドウで、ダウンロードしたサンプル Azure Digital Twins と、前の手順で解凍した IoT Hub 統合に移動した後、デバイスシミュレーター ディレクトリに移動します。 次に、次のコマンドを使用して、プロジェクトの依存関係をインストールします。

npm install

次に、デバイス シミュレーター ディレクトリで、.env という名前の新しいファイルに .env.template ファイルをコピーし、次の値を収集して設定を入力します。

  • PROVISIONING_IDSCOPE: この値を取得するには、 Azure portalでデバイス プロビジョニング サービスに移動し、メニュー オプションの [概要] を選択して、フィールド [ID スコープ] を探します。

    Screenshot of the Azure portal view of the device provisioning overview page highlighting the ID Scope value.

  • PROVISIONING_REGISTRATION_ID: デバイスの登録 ID を選択できます。

  • ADT_MODEL_ID: dtmi:contosocom:DigitalTwins:Thermostat;1

  • PROVISIONING_SYMMETRIC_KEY: この環境変数は、前に設定した登録の主キーです。 この値を再度取得するには、Azure portal で デバイス プロビジョニング サービスに移動し、[登録の管理] を選択してから、前に作成した登録グループを選択して、主キーをコピーします。

    Screenshot of the Azure portal view of the device provisioning service manage enrollments page highlighting the SAS primary key value.

ここで、上記の値を使用して、.env ファイルの設定を更新します。

PROVISIONING_HOST = "global.azure-devices-provisioning.net"
PROVISIONING_IDSCOPE = "<Device-Provisioning-Service-Scope-ID>"
PROVISIONING_REGISTRATION_ID = "<Device-Registration-ID>"
ADT_MODEL_ID = "dtmi:contosocom:DigitalTwins:Thermostat;1"
PROVISIONING_SYMMETRIC_KEY = "<Device-Provisioning-Service-enrollment-primary-SAS-key>"

ファイルを保存して閉じます。

デバイス シミュレーターの実行を開始

引き続き、コマンド ウィンドウの device-simulator ディレクトリで、次のコマンドを使用してデバイス シミュレーターを開始します。

node .\adt_custom_register.js

デバイスが登録されて IoT Hub に接続され、メッセージの送信が開始されるのがわかります。 Screenshot of the Command window showing device registration and sending messages.

検証

この記事で設定したフローの結果として、デバイスが Azure Digital Twins に自動的に登録されます。 次の Azure Digital Twins CLI コマンドを使用して、作成した Azure Digital Twins インスタンス内のデバイスのツインを検索します。 インスタンスのホスト名にはプレースホルダーが 1 つあり (インスタンスのフレンドリ名を使用することもできますが、パフォーマンスが若干低下します)、デバイス登録 ID にもプレースホルダーが 1 つあります。

az dt twin show --dt-name <instance-hostname-or-name> --twin-id "<device-registration-ID>"

Azure Digital Twins インスタンス内にデバイスのツインがあることを確認します。 Screenshot of the Command window showing newly created twin.

IoT Hub ライフサイクル イベントを使用してデバイスを自動的に廃止する

このセクションでは、デバイスを自動的に廃止するために、次のパスを使用して IoT Hub ライフサイクル イベントを Azure Digital Twins に接続します。 この図は、先ほど示したアーキテクチャの全体図からの抜粋です。

Diagram of the Retire device flow—an excerpt of the solution architecture diagram, following data from a device deletion into Azure Digital Twins.

プロセス フローの説明は次のとおりです。

  1. 外部または手動のプロセスによって、IoT Hub 内のデバイスの削除がトリガーされます。
  2. IoT Hub によってデバイスが削除され、デバイスのライフサイクル イベントが生成されます。それがイベント ハブにルーティングされます。
  3. Azure 関数によって、Azure Digital Twins 内のデバイスのツインが削除されます。

以下のセクションでは、このデバイス自動廃止フローを設定する手順について説明します。

イベント ハブの作成

次に、Azure イベント ハブを作成して IoT Hub ライフサイクル イベントを受け取ります。

イベント ハブを作成する」クイックスタートに記載されている手順に従います。 イベントハブに lifecycleeventsという名前を付けます。 このイベント ハブ名は、次のセクションで IoT Hub ルートと Azure 関数を設定するときに使用します。

次のスクリーンショットは、イベント ハブの作成を示しています。 Screenshot of the Azure portal window showing how to create an event hub with the name lifecycleevents.

イベント ハブの SAS ポリシーを作成する

次に、 Shared Access Signature (SAS) ポリシー を作成して、関数アプリでイベント ハブを構成する必要があります。 SAS ポリシーを作成するには:

  1. Azure portal で作成したイベント ハブに移動し、左側のメニュー オプションで [共有アクセス ポリシー] を選択します。
  2. [追加] を選択します。 開いた [SAS ポリシーの追加] ウィンドウで、任意のポリシー名を入力し、[リッスン] チェックボックスを選択します。
  3. [作成] を選択します。

Screenshot of the Azure portal showing how to add an event hub SAS policy.

関数アプリを使用してイベント ハブを構成する

次に、「前提条件」セクションで設定した Azure 関数アプリを、新しいイベント ハブで動作するように構成します。 イベント ハブの接続文字列を使用して、関数アプリ内に環境変数を設定することにより、関数を構成します。

  1. 作成したポリシーを開き、[接続文字列 – 主キー] の値をコピーします。

    Screenshot of the Azure portal showing how to copy the connection string-primary key.

  2. 次の Azure CLI コマンドを使用して、関数アプリの設定に変数として接続文字列を追加します。 このコマンドは、Cloud Shell で実行するか、Azure CLI がマシンにインストールされている場合はローカルで実行できます。

    az functionapp config appsettings set --settings "EVENTHUB_CONNECTIONSTRING=<Event-Hubs-SAS-connection-string-Listen>" --resource-group <resource-group> --name <your-function-app-name>
    

IoT Hub ライフサイクル イベントを使用してインベントリから削除する関数を追加する

「前提条件」セクションで作成した関数アプリ プロジェクト内に、IoT Hub ライフサイクル イベントを使用して既存のデバイスを削除する新しい関数を作成します。

ライフサイクル イベントの詳細については、IoT Hub の「非テレメトリ イベント」を参照してください。 Azure Functions で Event Hubs を使用する方法の詳細については、"Azure Functions に対する Azure Event Hubs トリガー" に関するページを参照してください。

マシン上の関数アプリに移動し、以下の手順に従ってください。

  1. 最初に、関数アプリ プロジェクトに、種類がイベント ハブ トリガーの新しい関数を作成します。

  2. 新しい NuGet パッケージをプロジェクトに追加します: Microsoft.Azure.Devices.Provisioning.Servic。 コード内で使用されているパッケージが既にプロジェクトに含まれていない場合は、さらにパッケージをプロジェクトに追加することが必要になる場合があります。

  3. 新しく作成された関数コード ファイルに次のコードを貼り付け、関数の名前を DeleteDeviceInTwinFunc.cs に指定し、ファイルを保存します。

    // Copyright (c) Microsoft. All rights reserved.
    // Licensed under the MIT license. See LICENSE file in the project root for full license information.
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Azure;
    using Azure.Core.Pipeline;
    using Azure.DigitalTwins.Core;
    using Azure.Identity;
    using Microsoft.Azure.EventHubs;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Extensions.Logging;
    
    namespace Samples.AdtIothub
    {
        public static class DeleteDeviceInTwinFunc
        {
            private static string adtAppId = "https://digitaltwins.azure.net";
            private static readonly string adtInstanceUrl = Environment.GetEnvironmentVariable("ADT_SERVICE_URL", EnvironmentVariableTarget.Process);
            private static readonly HttpClient singletonHttpClientInstance = new HttpClient();
    
            [FunctionName("DeleteDeviceInTwinFunc")]
            public static async Task Run(
                [EventHubTrigger("lifecycleevents", Connection = "EVENTHUB_CONNECTIONSTRING")] EventData[] events, ILogger log)
            {
                var exceptions = new List<Exception>(events.Length);
    
                // Create Digital Twin client
                var cred = new ManagedIdentityCredential(adtAppId);
                var client = new DigitalTwinsClient(
                    new Uri(adtInstanceUrl),
                    cred,
                    new DigitalTwinsClientOptions
                    {
                        Transport = new HttpClientTransport(singletonHttpClientInstance)
                    });
    
                foreach (EventData eventData in events)
                {
                    try
                    {
                        //log.LogDebug($"EventData: {System.Text.Json.JsonSerializer.Serialize(eventData)}");
    
                        string opType = eventData.Properties["opType"] as string;
                        if (opType == "deleteDeviceIdentity")
                        {
                            string deviceId = eventData.Properties["deviceId"] as string;
    
                            try
                            {
                                // Find twin based on the original Registration ID
                                BasicDigitalTwin digitalTwin = await client.GetDigitalTwinAsync<BasicDigitalTwin>(deviceId);
    
                                // In order to delete the twin, all relationships must first be removed
                                await DeleteAllRelationshipsAsync(client, digitalTwin.Id, log);
    
                                // Delete the twin
                                await client.DeleteDigitalTwinAsync(digitalTwin.Id, digitalTwin.ETag);
                                log.LogInformation($"Twin {digitalTwin.Id} deleted in DT");
                            }
                            catch (RequestFailedException e) when (e.Status == (int)HttpStatusCode.NotFound)
                            {
                                log.LogWarning($"Twin {deviceId} not found in DT");
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        // We need to keep processing the rest of the batch - capture this exception and continue.
                        exceptions.Add(e);
                    }
                }
    
                if (exceptions.Count > 1)
                    throw new AggregateException(exceptions);
    
                if (exceptions.Count == 1)
                    throw exceptions.Single();
            }
    
            /// <summary>
            /// Deletes all outgoing and incoming relationships from a specified digital twin
            /// </summary>
            public static async Task DeleteAllRelationshipsAsync(DigitalTwinsClient client, string dtId, ILogger log)
            {
                AsyncPageable<BasicRelationship> relationships = client.GetRelationshipsAsync<BasicRelationship>(dtId);
                await foreach (BasicRelationship relationship in relationships)
                {
                    await client.DeleteRelationshipAsync(dtId, relationship.Id, relationship.ETag);
                    log.LogInformation($"Twin {dtId} relationship {relationship.Id} deleted in DT");
                }
    
                AsyncPageable<IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(dtId);
                await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
                {
                    await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId);
                    log.LogInformation($"Twin {dtId} incoming relationship {incomingRelationship.RelationshipId} from {incomingRelationship.SourceId} deleted in DT");
                }
            }
        }
    }
    
  4. DeleteDeviceInTwinFunc.cs 関数が含まれるプロジェクトを Azure の関数アプリに発行します。

    Visual Studio を使用して関数を発行する方法については、「Visual Studio を使用する Azure Functions の開発」を参照してください。 Visual Studio Code を使用して関数を発行する方法については、「Visual Studio Code を使用して Azure に C# 関数を作成する」を参照してください。 Azure CLI を使用して関数を発行する方法については、「Azure でコマンド ラインから C# 関数を作成する」を参照してください。

重要

前提条件」セクションで関数アプリを初めて作成する場合、その関数のアクセス ロールが既に割り当てられていて、Azure Digital Twins インスタンスにアクセスするためのアプリケーション設定が構成されている可能性があります。 これらは、関数アプリ全体に対して 1 回実行する必要があるため、続行する前にアプリで完了したことを確認してください。 手順については、"アプリ認証コードの記述" に関する記事の「発行されたアプリを構成する」セクションを参照してください。

ライフサイクル イベントの IoT Hub ルートの作成

次に、デバイスのライフサイクル イベントをルーティングする IoT Hub ルートを設定します。 この場合、具体的には if (opType == "deleteDeviceIdentity") によって識別されるデバイス削除イベントをリッスンします。 このイベントにより、デジタル ツイン項目の削除がトリガーされ、デバイスとそのデジタル ツインの廃止プロセスが完了します。

まず、IoT ハブでイベント ハブ エンドポイントを作成する必要があります。 次に、IoT ハブにルートを追加して、ライフサイクルイベントをこのイベント ハブ エンドポイントに送信します。 イベント ハブ エンドポイントを作成するには、次の手順に従います。

  1. Azure portalで、「前提条件」セクションで作成した IoT ハブに移動し、左側のメニュー オプションで [メッセージ ルーティング] を選択します。

  2. [カスタム エンドポイント] タブを選択します。

  3. [+ 追加] を選択し、[イベント ハブ] を選択して、種類がイベント ハブのエンドポイントを追加します。

    Screenshot of the Azure portal showing how to add an Event Hubs custom endpoint.

  4. 開いた [イベント ハブ エンドポイントを追加] ウィンドウで次の値を選択します。

    • エンドポイント名: エンドポイント名を選択します。
    • イベント ハブの名前空間: ドロップダウン リストからイベント ハブの名前空間を選択します。
    • イベント ハブ インスタンス: 前の手順で作成したイベント ハブ名を選択します。
  5. [作成] を選択します。 このウィンドウを開いたままにして、次の手順でルートを追加します。

    Screenshot of the Azure portal showing how to add an event hub endpoint.

次に、前の手順で作成したエンドポイントに接続するルートを追加し、削除イベントを送信するルーティング クエリを追加します。 これらの手順に従って、ルートを作成します。

  1. [ルート] タブに移動し、[追加] を選択してルートを追加します。

    Screenshot of the Azure portal showing how to add a route to send events.

  2. 開いた [ルートの追加] ページで、次の値を選択します。

    • 名前: ルートの名前を選択します。
    • エンドポイント: ドロップダウン リストから、以前に作成したイベント ハブ エンドポイントを選択します。
    • データソース: デバイスのライフサイクル イベントを選択します。
    • ルーティング クエリ:「opType='deleteDeviceIdentity'」と入力します。 このクエリでは、削除イベントのみを送信するようにデバイスのライフサイクル イベントを制限します。
  3. [保存] を選択します。

    Screenshot of the Azure portal showing how to add a route to send lifecycle events.

このフローを終了すると、デバイスを廃止するための、エンドツーエンドのすべてのものが設定されます。

検証

廃止のプロセスをトリガーするには、デバイスを IoT Hub から手動で削除する必要があります。

Azure CLI コマンドを使用するか、Azure portal で、IoT Hub からデバイスを手動で削除できます。 次の手順に従って、Azure portal 内のデバイスを削除します。

  1. IoT ハブに移動し、左側のメニューオプションで [IoT デバイス] を選択します。
  2. この記事の前半で選択したデバイス登録 ID を持つデバイスが表示されます。 また、Azure Digital Twins にツインがある場合は、削除するその他のデバイスを選択して、デバイスが削除された後にツインが自動的に削除されることを確認できます。
  3. デバイスを選択し、[削除] を選択します。

Screenshot of the Azure portal showing how to delete device twin from the IoT devices.

Azure Digital Twins に反映された変更が表示されるまでに数分かかる場合があります。

次の Azure Digital Twins CLI コマンドを使用して、Azure Digital Twins インスタンス内のデバイスのツインが削除されたことを確認します。 インスタンスのホスト名にはプレースホルダーが 1 つあり (インスタンスのフレンドリ名を使用することもできますが、パフォーマンスが若干低下します)、デバイス登録 ID にもプレースホルダーが 1 つあります。

az dt twin show --dt-name <instance-hostname-or-name> --twin-id "<device-registration-ID>"

デバイスのツインが Azure Digital Twins インスタンス内に見つからなくなったことがわかります。

Screenshot of the Command window showing that the twin can't be found anymore.

リソースをクリーンアップする

この記事で作成したリソースがもう必要ない場合は、次の手順に従って削除します。

Azure Cloud Shell またはローカルの Azure CLI から az group delete コマンドを使用すると、リソース グループ内の Azure リソースをすべて削除できます。 このコマンドにより、リソース グループが削除され、Azure Digital Twins インスタンス、IoT ハブとハブ デバイスの登録、Event Grid トピックとそれに関連するサブスクリプション、イベント ハブ名前空間と両方の Azure Functions アプリが、ストレージなどの関連するリソースを含めて削除されます。

重要

リソース グループを削除すると、元に戻すことができません。 リソース グループとそこに含まれるすべてのリソースは完全に削除されます。 間違ったリソース グループやリソースをうっかり削除しないようにしてください。

az group delete --name <your-resource-group>

次に、ダウンロードしたプロジェクトのサンプル フォルダーをローカル コンピューターから削除します。

次のステップ

デバイスに対して作成したデジタル ツインは、Azure Digital Twins にフラット階層として格納されますが、モデル情報と組織の複数レベルの階層を使用して強化することができます。 この概念の詳細については、以下を参照してください。

Azure 関数で HTTP 要求を使用する方法の詳細については次を参照してください。

Azure Digital Twins に既に格納されているモデルとグラフ データを使用して、この情報を自動的に提供するカスタム ロジックを作成できます。 ツイン グラフの情報の管理、アップグレード、および取得について詳しくは、以下の方法ガイドをご覧ください。