Azure Device Provisioning Service (DPS) でデバイス自動登録をやってみた(対称キーを使用)

Azure Device Provisioning Service (DPS) でデバイス自動登録をやってみた(対称キーを使用)

Clock Icon2025.05.07

はじめに

コンサルティング部の神野です。
Azure Device Provisioning Service (以下DPS) を初めて使ってみたのでブログにしてみました。

DPSを使うと、デバイスが初めてインターネットに繋がったときに、自動的にAzure IoT Hubにデバイスが登録可能な仕組みが作れます。
今回の一連の作業をブログに記載しました。

今回やることの概要

簡単な表現にすると下記ステップで進めていきます。

  1. Azure IoT Hubの作成
    • デバイスからのメッセージを受け取る「玄関」を作ります。
  2. Azure IoT Hub Device Provisioning Service (DPS) の作成
    • デバイスをIoT Hubに自動で案内してくれる「受付」を作ります。
  3. IoT HubとDPSのリンク
    • 「玄関」と「受付」を繋ぎます。
  4. DPSに個別登録の作成
    • 「受付」に、特定のデバイス(今回はPythonスクリプトでシミュレート)の情報を登録します。ここでは「対称キー」という合言葉を使います。
  5. Pythonスクリプトでデバイスをシミュレート
    • パソコン上でPythonスクリプトを実行し、仮想的なIoTデバイスとしてDPSに接続、IoT Hubへの自動登録を試します。
  6. 動作確認
    • 無事にIoT Hubにデバイスが登録され、メッセージを送れるか確認します。

プロビジョニングのイメージ

CleanShot 2025-05-07 at 08.59.56@2x

準備するもの

  • Azureアカウント
  • Python実行環境: Python 3.9以上
  • Azure CLI
  • テキストエディタ: Pythonスクリプトを編集するために使います。

Azure IoT Hub を作成する

まずは、デバイスからのデータを受け取るための「IoT Hub」を作成しましょう。

  1. Azure Portalにログインし、検索ボックスに「IoT Hub」と入力し、表示された「IoT Hub」を選択して「+ 作成」をクリックします。
    CleanShot 2025-05-07 at 09.06.35@2x
  2. 基本設定:
    • サブスクリプション: ご自身のサブスクリプションを選択します。
    • リソースグループ: ご自身のリソースグループを選択します。
    • IoT Hub名: 任意の名前を追加します。(例: sample-iot-hub)。エラーが出たら別の名前に変更してください。
    • 地域: 利用するリージョンを選択します(例: Japan East )。
    • レベル: ここではテストなので、一番安価な 無料 を選択します。
      CleanShot 2025-05-07 at 09.07.32@2x
  3. ネットワーク設定:
    • 「パブリックアクセス」が選択されていることを確認します。今回はシンプルにインターネット経由で接続します。
      CleanShot 2025-05-07 at 09.08.49@2x
  4. 管理設定:
    • 特に変更は不要です。デフォルトのままでOKです。
      CleanShot 2025-05-07 at 09.09.06@2x
  5. アドオン設定:
    • 今回はチェック不要です。
      CleanShot 2025-05-07 at 09.09.23@2x
  6. タグ設定:
    • 任意でタグを設定できます(例: envキーにtestバリュー)。今回は省略しても構いません。
  7. 確認および作成:
    • 設定内容を確認し、「作成」をクリックします。
    • デプロイが完了するまで数分待ちます。「デプロイが完了しました」と表示されたら、「リソースに移動」をクリックして作成したIoT Hubのページを開いておきましょう。
      CleanShot 2025-05-07 at 09.10.23@2x 1

これで、IoT Hubの準備ができました!

Azure IoT Hub Device Provisioning Service (DPS) を作成する

次に、デバイスの自動登録を担当する「DPS」を作成します。

  1. Azure PortalでAzure IoT Hub Device Provisioning Serviceの画面を開き左上の「+作成」をクリックします。
    CleanShot 2025-05-07 at 09.33.44@2x
  2. 基本設定
    • サブスクリプション: ご自身のサブスクリプションを選択します。
    • リソースグループ: 先ほどIoT Hubを作成した時と同じリソースグループを選択します。
    • 名前: グローバルで一意なDPSインスタンス名を付けます(例: sample-dps-test)。
    • 地域: IoT Hubと同じリージョンを選択します。(例: (Japan East)。
      CleanShot 2025-05-07 at 09.35.05@2x
  3. ネットワーク設定
    • 「パブリックアクセス」が選択されていることを確認します。今回はシンプルにインターネット経由で接続します。
      CleanShot 2025-05-07 at 09.35.53@2x
  4. タグ設定
    • 任意です。今回は省略してもOKです。
  5. 確認および作成
    • 設定内容を確認し、「作成」をクリックします。
    • デプロイが完了するまで数分待ちます。「デプロイが完了しました」と表示されたら、「リソースに移動」をクリックして作成したDPSのページを開いておきましょう。
      CleanShot 2025-05-07 at 09.36.19@2x 2

DPSインスタンスも無事作成できましたね!

IoT Hub と DPS をリンクする

作成したDPSが、どのIoT Hubにデバイスを登録すればよいか分かるように、両者をリンク(紐付け)します。

  1. 作成したDPSのポータルページを開きます。
  2. 左側のメニューから「設定」セクションの「登録を管理します」を選択します。
  3. 上部にある「+登録グループの追加」ボタンをクリックします。
    CleanShot 2025-05-07 at 09.40.00@2x 1
  4. 登録とプロビジョニング
    • 構成対称メカニズムは対称キーを選択
      • 対称キーの設定は対称キーを自動的に生成しますにチェックを入れる
    • グループ名は任意の名前を選択(例:sample-dps-group
    • プロビジョニングの状態はこの登録を有効にするにチェックをいれる
      CleanShot 2025-05-07 at 09.41.06@2x
  5. IoT Hubのリンクを追加
    • IoT ハブへのリンクを追加しますリンクを押下
      CleanShot 2025-05-07 at 09.42.24@2x
    • サブスクリプション: ご自身のサブスクリプションを選択
    • IoT Hub: プルダウンから、事前に作成したIoT Hub(例: sample-iot-hub)を選択します。
    • アクセス ポリシー: iothubowner (自動的に選択されます) のままでOKです。これはDPSがIoT Hubにデバイスを登録するための権限です。
    • 「保存」をクリックします。
      CleanShot 2025-05-07 at 09.43.20@2x 1
  6. IoTハブ
    • 先ほど保存したIoT ハブをターゲット IoT ハブとして選択、割り当てポリシーはデフォルトのままでOKです。
      CleanShot 2025-05-07 at 09.47.01@2x
  7. デバイス設定
    • 今回は使用しないのでそのままでOKです。
  8. 確認と作成
    • 登録する内容を確認して問題なければ作成ボタンを押下します。
      CleanShot 2025-05-07 at 09.51.48@2x

これで、「受付(DPS)」が「玄関(IoT Hub)」の場所を覚えました。

Azure CLI を使って【既存の登録グループ】からデバイス固有キーを導出する

この手順で得られた「デバイスの登録ID」と「導出されたデバイス固有キー」をメモし、後ほどPythonスクリプトでデバイスをシミュレートする際に使用します。

1. デバイス固有キー導出のための情報準備

デバイス固有キーを導出するために、以下の情報が必要になります。

  1. 既存の登録グループの主キー (Primary Symmetric Key)
    • これは、作成済みの登録グループに設定されている対称キーです。
    • ポータルの下記値をコピーください。
      CleanShot 2025-05-07 at 10.21.21@2x
  2. デバイスの登録ID (Device Registration ID)
    • これからプロビジョニングし、デバイス固有キーを導出したい個々のデバイスを識別するためのIDです。
    • このIDは、登録グループ内でユニークである必要があります。
    • 小文字の英数字とハイフンのみが使用可能です。
    • これはデバイスごと固有の値を設定します (例: my-simulated-device-001)。
    • このIDが、後ほどPythonスクリプトの PROVISIONING_REGISTRATION_ID になります。

2. Azure CLI でデバイス固有キーを導出する

必要な情報が準備できたら、ターミナルを開き、以下のAzure CLIコマンドを実行して、デバイス固有の対称キーを導出します。

実行コマンド
az iot dps enrollment-group compute-device-key --key <YOUR_ENROLLMENT_GROUP_PRIMARY_OR_SECONDARY_KEY> \
    --registration-id <YOUR_CHOSEN_DEVICE_REGISTRATION_ID>

コマンドの解説

  • az iot dps enrollment-group compute-device-key: 登録グループの対称キー(主キーまたはセカンダリキー)とデバイスの登録IDから、そのデバイス専用の対称キーを計算(導出)するコマンドです。
  • --key: ここには、手順1で準備した既存の登録グループの主キー(またはセカンダリキー)の値を指定します。
  • --registration-id: ここには、手順1で準備した(ユーザー様が決定した)デバイスの登録IDの値を指定します。

このコマンドを実行すると、指定したデバイス登録IDに対応するデバイス固有の対称キーが、Base64エンコードされた文字列としてターミナルに出力されます。

対称キーのイメージ
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

3. 必要な情報をメモする

ここで、次のPythonスクリプトで使用するために、以下の2つの情報を確実にメモしておきましょう。

  1. デバイスの登録ID (Device Registration ID): 手順1で準備し、手順2の --registration-id で使用した、デバイス固有の登録ID (例: my-simulated-device-001)。
  2. 導出されたデバイス固有キー (Derived Device Symmetric Key): 手順2の compute-device-key コマンドを実行してターミナルに出力された、デバイス固有の対称キーの値 (例: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=)。

これらの値が、この後のPythonスクリプトでデバイスをシミュレートし、DPSに接続してIoT Hubにプロビジョニングされるために必要です。 デバイスは、この「デバイスの登録ID」と「導出されたデバイス固有キー」を使ってDPSに認証を行います。
このやり方は公式ドキュメントにも記載がありますので、必要に応じてご参照ください。

https://learn.microsoft.com/ja-jp/azure/iot-dps/how-to-legacy-device-symm-key?tabs=azure-cli&pivots=programming-language-python#derive-a-device-key

これで、Azure CLI を使って、既存のDPS登録グループから特定のデバイスのための固有キーを導出し、次のステップに進むための準備が整いました!

Pythonスクリプトでデバイスをシミュレートする

いよいよ、パソコン上でPythonスクリプトを実行して、仮想的なIoTデバイスとしてDPSに接続し、自動プロビジョニングを試します。

  1. 必要なPythonライブラリのインストール
    ターミナル(WindowsならコマンドプロンプトやPowerShell、Macならターミナル.app)を開き、以下のコマンドを実行して、Azure IoT Device SDKをインストールします。

    pip install azure-iot-device
    

    「Successfully installed...」のようなメッセージが出ればOKです。

  2. Pythonスクリプトの作成
    お好きなテキストエディタを開き、以下のコードを貼り付けて、dps_device_test.py という名前で保存します。保存場所はどこでも構いません(例: デスクトップ)。

コード全体
# dps_device_test.py
import time
import json
import logging
import datetime
import ssl
from azure.iot.device import ProvisioningDeviceClient, IoTHubDeviceClient, Message, exceptions

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

PROVISIONING_ID_SCOPE = "PROVISIONING_ID_SCOPE"
PROVISIONING_REGISTRATION_ID = "YOUR_DEVICE_REGISTRATION_ID"
PROVISIONING_SYMMETRIC_KEY = "PROVISIONING_SYMMETRIC_KEY"

# DPSグローバルエンドポイント (通常はこのままでOK)
PROVISIONING_GLOBAL_DEVICE_ENDPOINT = "global.azure-devices-provisioning.net"

def provision_device():
    try:
        logger.info("===== デバイスプロビジョニング開始 =====")
        logger.info(f"ID スコープ: {PROVISIONING_ID_SCOPE}")
        logger.info(f"登録 ID: {PROVISIONING_REGISTRATION_ID}")
        logger.debug(f"DPSエンドポイント: {PROVISIONING_GLOBAL_DEVICE_ENDPOINT}")

        logger.debug("プロビジョニングクライアントの作成...")

        provisioning_client_kwargs = {
            "provisioning_host": PROVISIONING_GLOBAL_DEVICE_ENDPOINT,
            "registration_id": PROVISIONING_REGISTRATION_ID,
            "id_scope": PROVISIONING_ID_SCOPE,
            "symmetric_key": PROVISIONING_SYMMETRIC_KEY,
        }

        enrollment_type = "group" if "group" in PROVISIONING_REGISTRATION_ID.lower() else "individual"
        logger.info(f"登録タイプ: {enrollment_type}")

        provisioning_client = ProvisioningDeviceClient.create_from_symmetric_key(**provisioning_client_kwargs)

        logger.info("DPSに登録中... (しばらく時間がかかる場合があります)")
        start_time = time.time()
        registration_result = provisioning_client.register()
        elapsed_time = time.time() - start_time

        logger.info(f"DPS登録ステータス: {registration_result.status} (処理時間: {elapsed_time:.2f}秒)")
        logger.debug(f"登録結果の詳細: {registration_result}")

        if registration_result.status == "assigned":
            logger.info(f"デバイスは以下のIoT Hubに割り当てられました: {registration_result.registration_state.assigned_hub}")
            logger.info(f"IoT Hub内のデバイスID: {registration_result.registration_state.device_id}")

            if hasattr(registration_result.registration_state, 'created_date_time'):
                logger.debug(f"作成日時: {registration_result.registration_state.created_date_time}")
            if hasattr(registration_result.registration_state, 'last_update_date_time'):
                logger.debug(f"最終更新日時: {registration_result.registration_state.last_update_date_time}")
            if hasattr(registration_result.registration_state, 'etag'):
                logger.debug(f"ETag: {registration_result.registration_state.etag}")

            return registration_result.registration_state.assigned_hub, registration_result.registration_state.device_id
        else:
            logger.error(f"DPS登録失敗. エラー: {registration_result.registration_state.error_code} - {registration_result.registration_state.error_message}")
            if hasattr(registration_result.registration_state, 'status') and registration_result.registration_state.status:
                logger.error(f"DPS詳細ステータス: {registration_result.registration_state.status}")
            return None, None

    except exceptions.CredentialError as e:
        logger.error(f"DPS認証エラー: {e}")
        logger.error("ID スコープ、登録 ID、対称キーを再確認してください。")
        return None, None
    except exceptions.ConnectionFailedError as e:
        logger.error(f"DPS接続エラー: {e}")
        logger.error("インターネット接続とDPSエンドポイントを確認してください。")
        return None, None
    except ssl.SSLCertVerificationError as e:
        logger.error(f"SSL証明書検証エラー: {e}")
        return None, None
    except Exception as e:
        logger.error(f"DPS登録中に予期しないエラーが発生しました: {e}", exc_info=True)
        return None, None

def send_telemetry(iot_hub_host, device_id):
    try:
        logger.info(f"===== IoT Hubクライアント作成 =====")
        logger.info(f"IoT Hubホスト: {iot_hub_host}")
        logger.info(f"デバイスID: {device_id}")

        conn_str = f"HostName={iot_hub_host};DeviceId={device_id};SharedAccessKey={PROVISIONING_SYMMETRIC_KEY}"
        logger.debug("接続文字列を使用してIoT Hubクライアントを作成中...")

        iot_hub_client_kwargs = {}

        start_time = time.time()
        iot_hub_client = IoTHubDeviceClient.create_from_connection_string(conn_str, **iot_hub_client_kwargs)
        elapsed_time = time.time() - start_time
        logger.debug(f"IoT Hubクライアント作成完了 (処理時間: {elapsed_time:.2f}秒)")

        logger.debug("IoT Hubに接続中...")
        iot_hub_client.connect()
        logger.info("IoT Hubに正常に接続されました")

        timestamp = datetime.datetime.now().isoformat()
        message_payload = {
            "temperature": 25.5, 
            "humidity": 60.1, 
            "message": "Hello from Python DPS device!",
            "timestamp": timestamp,
            "deviceId": device_id
        }

        message_json = json.dumps(message_payload)
        logger.info(f"テレメトリメッセージを送信中...")
        logger.debug(f"メッセージ内容: {message_json}")

        message = Message(message_json)
        message.content_encoding = "utf-8"
        message.content_type = "application/json"
        message.message_id = f"{device_id}-{int(time.time())}"

        message.custom_properties["temperatureAlert"] = "false" if message_payload["temperature"] < 30 else "true"
        logger.debug(f"メッセージID: {message.message_id}")
        logger.debug(f"カスタムプロパティ: {message.custom_properties}")

        start_time = time.time()
        iot_hub_client.send_message(message)
        elapsed_time = time.time() - start_time
        logger.info(f"テレメトリメッセージが正常に送信されました (処理時間: {elapsed_time:.2f}秒)")

        logger.debug("IoT Hubクライアントをシャットダウン中...")
        iot_hub_client.shutdown()
        logger.info("IoT Hubクライアントが正常にシャットダウンされました")

    except exceptions.ConnectionDroppedError as e:
        logger.error(f"IoT Hub接続が切断されました: {e}")
        logger.error("ネットワーク接続とIoT Hubの状態を確認してください。")
    except exceptions.ConnectionFailedError as e:
        logger.error(f"IoT Hub接続に失敗しました: {e}")
        logger.error("ホスト名、デバイスID、共有アクセスキーを確認してください。")
    except exceptions.CredentialError as e:
        logger.error(f"IoT Hub認証エラー: {e}")
        logger.error("デバイスキーとデバイスIDを確認してください。")
    except exceptions.ClientError as e:
        logger.error(f"IoT Hubクライアントエラー: {e}")
    except ssl.SSLCertVerificationError as e:
        logger.error(f"SSL証明書検証エラー: {e}")
    except Exception as e:
        logger.error(f"IoT Hub通信中に予期しないエラーが発生しました: {e}", exc_info=True)

if __name__ == "__main__":
    logger.info("===== Azure IoT Hub DPS デバイスシミュレータ =====")
    logger.info(f"実行時刻: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

    if PROVISIONING_ID_SCOPE == "YOUR_DPS_ID_SCOPE":
        logger.error("エラー: スクリプト内の 'PROVISIONING_ID_SCOPE' をあなたのDPS IDスコープで更新してください。")
        exit()
    if PROVISIONING_REGISTRATION_ID == "YOUR_DEVICE_REGISTRATION_ID":
        logger.error("エラー: スクリプト内の 'PROVISIONING_REGISTRATION_ID' をあなたのデバイス登録IDで更新してください。")
        exit()
    if PROVISIONING_SYMMETRIC_KEY == "YOUR_DEVICE_PRIMARY_KEY":
        logger.error("エラー: スクリプト内の 'PROVISIONING_SYMMETRIC_KEY' をあなたのデバイスキーで更新してください。")
        exit()

    logger.info("デバイスプロビジョニングを開始します...")
    assigned_hub, device_id_in_hub = provision_device()

    if assigned_hub and device_id_in_hub:
        logger.info("===== デバイスプロビジョニング成功 =====")
        send_telemetry(assigned_hub, device_id_in_hub)
    else:
        logger.error("===== デバイスプロビジョニング失敗 =====")
        logger.error("ログとDPS/デバイス設定を確認してください。")

    logger.info("===== シミュレーション終了 =====")   
  1. スクリプトの修正
    保存した dps_device_test.py をテキストエディタで開き、以下の3箇所を、ご自身の情報に書き換えてください。

    • PROVISIONING_ID_SCOPE
      • Azure Portalで、作成したDPSインスタンス(例: sample-dps-test)の「概要」ページを開きます。
      • そこに表示されている「IDスコープ」の値をコピーして、スクリプトの "PROVISIONING_ID_SCOPE" の部分に貼り付けます。
        CleanShot 2025-05-07 at 10.24.11@2x
    • PROVISIONING_REGISTRATION_ID
      • 手順4でDPSの個別登録を作成した際に設定した「登録ID」(例: my-python-device-01)を、"PROVISIONING_REGISTRATION_ID" の部分に記述します。
    • PROVISIONING_SYMMETRIC_KEY
      • 手順4でDPSの個別登録の詳細画面からコピーした「主キー」を、"PROVISIONING_SYMMETRIC_KEY" の部分に貼り付けます。

    これらの値が間違っていると、スクリプトは正しく動作しませんので実行前にご確認ください。

  2. Pythonスクリプトの実行
    ターミナルで、dps_device_test.py を保存したディレクトリに移動し(cdコマンドを使います)、以下のコマンドでスクリプトを実行します。

    python dps_device_test.py
    

    スクリプトが実行されると、ターミナルに以下のようなログが表示されるはずです(一部の値はご自身の環境のものになります)。

    実行ログ全体
    2025-05-07 10:25:01 - INFO - ===== Azure IoT Hub DPS デバイスシミュレータ =====
    2025-05-07 10:25:01 - INFO - 実行時刻: 2025-05-07 10:25:01
    2025-05-07 10:25:01 - INFO - デバイスプロビジョニングを開始します...
    2025-05-07 10:25:01 - INFO - ===== デバイスプロビジョニング開始 =====
    2025-05-07 10:25:01 - INFO - ID スコープ: xxxxxxxx
    2025-05-07 10:25:01 - INFO - 登録 ID: sample-my-device-001
    2025-05-07 10:25:01 - INFO - 登録タイプ: individual
    2025-05-07 10:25:01 - INFO - Creating client for connecting using MQTT over TCP
    2025-05-07 10:25:01 - INFO - DPSに登録中... (しばらく時間がかかる場合があります)
    2025-05-07 10:25:01 - INFO - Registering with Provisioning Service...
    2025-05-07 10:25:01 - INFO - Enabling reception of response from Device Provisioning Service...
    2025-05-07 10:25:01 - INFO - Connect using port 8883 (TCP)
    2025-05-07 10:25:01 - INFO - connected with result code: 0
    2025-05-07 10:25:01 - INFO - _on_mqtt_connected called
    2025-05-07 10:25:01 - INFO - subscribing to $dps/registrations/res/# with qos 1
    2025-05-07 10:25:01 - INFO - suback received for 1
    2025-05-07 10:25:01 - INFO - Successfully subscribed to Device Provisioning Service to receive responses
    2025-05-07 10:25:01 - INFO - publishing on $dps/registrations/PUT/iotdps-register/?$rid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    2025-05-07 10:25:01 - INFO - payload published for 2
    2025-05-07 10:25:02 - INFO - message received on $dps/registrations/res/202/?$rid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&retry-after=3
    2025-05-07 10:25:04 - INFO - RegistrationStage(RequestAndResponseOperation): polling
    2025-05-07 10:25:04 - INFO - publishing on $dps/registrations/GET/iotdps-get-operationstatus/?$rid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&operationId=x.xxxxxxxxxx.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    2025-05-07 10:25:04 - INFO - payload published for 3
    2025-05-07 10:25:04 - INFO - message received on $dps/registrations/res/200/?$rid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    2025-05-07 10:25:04 - INFO - Successfully registered with Provisioning Service
    2025-05-07 10:25:04 - INFO - Forcing paho disconnect to prevent it from automatically reconnecting
    2025-05-07 10:25:04 - INFO - DPS登録ステータス: assigned (処理時間: 2.49秒)
    2025-05-07 10:25:04 - INFO - デバイスは以下のIoT Hubに割り当てられました: sample-iot-hub.azure-devices.net
    2025-05-07 10:25:04 - INFO - IoT Hub内のデバイスID: sample-my-device-001
    2025-05-07 10:25:04 - INFO - ===== デバイスプロビジョニング成功 =====
    2025-05-07 10:25:04 - INFO - ===== IoT Hubクライアント作成 =====
    2025-05-07 10:25:04 - INFO - IoT Hubホスト: sample-iot-hub.azure-devices.net
    2025-05-07 10:25:04 - INFO - デバイスID: sample-my-device-001
    2025-05-07 10:25:04 - INFO - Creating client for connecting using MQTT over TCP
    2025-05-07 10:25:04 - INFO - Connecting to Hub...
    2025-05-07 10:25:04 - INFO - Connect using port 8883 (TCP)
    2025-05-07 10:25:04 - INFO - connected with result code: 0
    2025-05-07 10:25:04 - INFO - _on_mqtt_connected called
    2025-05-07 10:25:04 - INFO - Connection State - Connected
    2025-05-07 10:25:04 - INFO - Successfully connected to Hub
    2025-05-07 10:25:04 - INFO - IoT Hubに正常に接続されました
    2025-05-07 10:25:04 - INFO - テレメトリメッセージを送信中...
    2025-05-07 10:25:04 - INFO - Sending message to Hub...
    2025-05-07 10:25:04 - INFO - publishing on devices/sample-my-device-001/messages/events/%24.mid=sample-my-device-001-xxxxxxxxxx&%24.ct=application%2Fjson&%24.ce=utf-8&temperatureAlert=false
    2025-05-07 10:25:04 - INFO - payload published for 1
    2025-05-07 10:25:04 - INFO - Successfully sent message to Hub
    2025-05-07 10:25:04 - INFO - テレメトリメッセージが正常に送信されました (処理時間: 0.24秒)
    2025-05-07 10:25:04 - INFO - Initiating client shutdown
    2025-05-07 10:25:04 - INFO - Disconnecting from Hub...
    2025-05-07 10:25:04 - INFO - disconnecting MQTT client
    2025-05-07 10:25:04 - INFO - disconnected with result code: 0
    2025-05-07 10:25:04 - INFO - MQTTTransportStage: _on_mqtt_disconnect called
    2025-05-07 10:25:04 - INFO - Connection State - Disconnected
    2025-05-07 10:25:04 - INFO - Cleared all pending method requests due to disconnect
    2025-05-07 10:25:04 - INFO - Successfully disconnected from Hub
    2025-05-07 10:25:04 - INFO - Forcing paho disconnect to prevent it from automatically reconnecting
    2025-05-07 10:25:04 - INFO - Client shutdown complete
    2025-05-07 10:25:04 - INFO - IoT Hubクライアントが正常にシャットダウンされました
    2025-05-07 10:25:04 - INFO - ===== シミュレーション終了 =====
    

DPS登録ステータス: assigned と表示され、割り当てられたIoT Hub名とデバイスIDが表示されれば、DPSによるプロビジョニングは成功です!
その後、IoT Hubにテストメッセージが送信されています。

もしエラーが出た場合は、エラーメッセージをよく読み、スクリプト内のIDスコープ、登録ID、対称キーが正しいか、DPSの登録設定が正しいかなどを確認してみてください。

動作確認

最後に、Azure PortalやAzure Cloud Shellを使って、本当にデバイスがIoT Hubに登録され、メッセージが届いているか確認しましょう。

  1. IoT Hubでのデバイス自動作成確認 (Azure Portal)

    • Azure Portalで、手順1で作成したIoT Hub(例: sample-iot-hub)のページを開きます。
    • 左側のメニューから「デバイス管理」セクションの「デバイス」を選択します。
    • 下記のようにデバイスの一覧に、Pythonスクリプトのログで確認できたデバイスIDでデバイスが自動的に作成され、「有効」状態になっていることを確認します。
      CleanShot 2025-05-07 at 10.27.50@2x 1
  2. IoT Hubへのメッセージ到着確認 (Azure Cloud Shell)
    IoT Hubに送信されたメッセージを確認する簡単な方法は、Azure Cloud Shellを使うことです。

    • Azure ポータルで右上の [>_] アイコンをクリックするか、https://portal.azure.com/#cloudshell/ にアクセスしてAzure Cloud Shellを起動します。

    • 初めて使用する場合は、ストレージアカウントの設定を求められるので、指示に従ってセットアップを完了します。

    • 次に、IoT Hub拡張機能をインストールします(初回のみ)。

      az extension add --name azure-iot
      
    • 以下のコマンドを実行して、IoT Hubに到着するメッセージを監視します。YourIoTHubName の部分は、ご自身のIoT Hub名(例: sample-iot-hub)に置き換えてください。

      az iot hub monitor-events --hub-name YourIoTHubName
      
    • Pythonスクリプトを再度実行すると、Cloud Shellに以下のようなイベントが表示されるはずです。

      {
          "event": {
              "origin": "sample-my-device-001",
              "module": "",
              "interface": "",
              "component": "",
              "payload": {
                  "temperature": 25.5,
                  "humidity": 60.1,
                  "message": "Hello from Python DPS device!",
                  "timestamp": "2025-05-07T10:42:49.657533",
                  "deviceId": "sample-my-device-001"
              }
          }
      }
      
    • payload の部分に、Pythonスクリプトから送信したメッセージの内容が含まれていれば、無事メッセージがIoT Hubに届いていることが確認できました!

    • 監視を停止するには、Ctrl+C を押します。

デバイスの登録およびメッセージの送信も問題なく実行できましたね!!

おわりに

今回は、Azure PortalとPythonスクリプトを使って、Azure IoT Hub Device Provisioning Service (DPS) によるデバイスの自動登録を体験してみました。いかがだったでしょうか?

DPSの主なメリットはデバイスのプロビジョニングを自動で行えることかと思います。

今回は「対称キー」を使った「個別登録」というシンプルな方法でしたが、DPSは多岐なシナリオ(X.509証明書を使った認証や、たくさんのデバイスをまとめて登録する「グループ登録」など)にも対応しています。この辺りもブログで紹介していけたらと思います!

この記事が少しでも参考になりましたら幸いです!

補足: Mac環境で証明書関連のエラーが出た場合

もし、macOS環境で上記のPythonスクリプトを実行した際に、コンソールに以下のような CERTIFICATE_VERIFY_FAILED というエラーメッセージが表示されることがあります。

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)

または

azure.iot.device.common.transport_exceptions.TlsExchangeAuthError: TlsExchangeAuthError(None) caused by SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)')

これは、PythonがSSL通信を行う際に必要なルート証明書を正しく見つけられない場合に発生することがあります。
この問題は、特にPythonを公式サイトからダウンロードしてインストールした場合などに起こりやすいです。

その場合は、以下の手順で対処できる可能性があります。

  1. certifi パッケージの更新
    ターミナルを開き、以下のコマンドを実行して、Pythonの証明書を管理する certifi パッケージを最新版にアップグレードします。

    pip install --upgrade certifi
    
  2. Pythonに付属の証明書インストールコマンドの実行
    次に、お使いのPythonのバージョンに応じた証明書インストール用のコマンドを実行します。
    Python 3.9 をお使いの場合は、ターミナルで以下のコマンドを実行してください。

    open /Applications/Python\ 3.9/Install\ Certificates.command
    

    もしPython 3.7や3.8など他のバージョンをお使いの場合は、パスの中の Python\ 3.9 の部分を、ご自身のPythonバージョン(例: Python\ 3.7Python\ 3.8)に置き換えて実行してください。
    このコマンドを実行すると、PythonがSSL接続に使用するための証明書がシステムにインストール(または正しくリンク)されます。

上記の手順を実行後、再度 python dps_device_test.py を実行して、エラーが解消されるか確認してみてください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.