Databricks にて Azure IoT Hub に対して Twin's desired properties の送受信を実施する方法

## 概要

Databricks にて Azure IoT SDK により Azure IoT Hub に対して Twin's desired properties を送受信する方法を紹介します。本記事で詳細する方法は、検証目的で実行する方法です。

## 事前準備

### 環境構築

- Azure IoT Hub の構築 （本手順は Free tier で実施）
- Databricks （本手順は Serveless ではなく汎用コンピュートで実行）

### デバイスの作成と接続文字列の取得

Azure IoT Hub のリソース画面にて、`デバイス`タブを選択後、`+ デバイスの追加`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/32708e89-0264-4b64-b5f2-494be974a7d4.png)

`デバイス ID`に任意の値を入力し、`保存`を選択してデバイスを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/37ac238e-cbd3-48d6-82f5-68cf9038354e.png)

作成したデバイスを表示して、`プライマリ接続文字列`の`コピー済み`を選択して値をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/4f8d5549-6a72-4ff4-9e10-0e23214201f6.png)

### 組み込みのエンドポイントの接続文字列を取得

Azure IoT Hub のリソース画面にて、`組み込みのエンドポイント`タブを選択して、`イベントハブ互換エンドポイント`の値をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/0d7b0ebb-998d-473d-8ee6-cbee3af3bbed.png)

### Azure IoT Hub の接続文字列を取得

Azure IoT Hub のリソース画面にて、`共有アクセス ポリシー`タブ -> `iothubowner`ポリシーを選択して、`プライマリ接続文字列`の値をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/ac063b7d-54f5-4dbd-a27a-92cde4a1a04a.png)

## Twin's desired properties の受信

Databricks にて下記のコードを実行して、Twin's desired プロパティの受信します。処理が 60 秒継続するようにしております。

```python
%pip install azure-iot-device -q
dbutils.library.restartPython()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/3dc83b65-11ed-4fbc-8d5c-3cbd177dd785.png)


```python
device_conn_str = "HostName=iot-hub-test-001.azure-devices.net;DeviceId=qiita_test_01;SharedAccessKey=r2fxXUdn/4w9iNJdhqnIJZ+h0qxxxxx="
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/cc3a2615-3d82-4c6b-8097-5dd170db527f.png)


```python
from datetime import datetime, timezone
import time
from azure.iot.device import IoTHubDeviceClient

# デバイス接続文字列（DeviceId 用）
DEVICE_CONN_STR = "...your device connection string..."

def sanitize(patch: dict) -> dict:
    """'$' を含むシステム メタデータや None を除外"""
    return {k: v for k, v in patch.items() if not k.startswith("$") and v is not None}

def twin_patch_handler(patch):
    # desired パッチを受信したら reported 側に即時反映
    print("⬇️ desired 受信:", patch)
    clean = sanitize(patch)
    clean["lastAppliedUtc"] = datetime.now(timezone.utc).isoformat()
    try:
        client.patch_twin_reported_properties(clean)
        print("✅ reported 送信:", clean)
    except Exception as exc:
        print("❌ 送信失敗:", exc)

client = IoTHubDeviceClient.create_from_connection_string(
    device_conn_str,
    websockets=True,          # ファイアウォール制約がある場合
)
client.on_twin_desired_properties_patch_received = twin_patch_handler
client.connect()

# デバイス起動時の heartbeat
client.patch_twin_reported_properties({"startedUtc": datetime.now(timezone.utc).isoformat()})

print("🟢 デバイス待受開始（60 秒）")
time.sleep(60)
client.shutdown()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/3c55e7ac-a4e6-4f8c-a913-4ef5b2b1aa54.png)

## Twin's desired properties の送信

Databricks にて別のノートブックを作成して、下記のコードを実行して、Twin's desired プロパティを送信します。

```python
%pip install azure-iot-hub -q
dbutils.library.restartPython()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/bc783f8e-99d1-46e7-bcf7-e27d49c0ddb5.png)


```python
iot_conn_str = "HostName=iot-hub-test-001.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=FUum/tiOgEuhye/JqAb+XXXXX="
device_id = "qiita_test_01"
```

```python
from datetime import datetime, timezone
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import Twin, TwinProperties

rm = IoTHubRegistryManager.from_connection_string(iot_conn_str)

twin = rm.get_twin(device_id)
current_etag = twin.etag

desired_patch = {
    "fwVersion": "2.3.0",
    "requestedUtc": datetime.now(timezone.utc).isoformat(),
}

patch = Twin(properties=TwinProperties(desired=desired_patch))

updated_twin = rm.update_twin(device_id, patch, current_etag)  # ★ ETag を渡す
print("✅ 更新後 desired:", updated_twin.properties.desired)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/5361fa68-2366-4ff8-a14a-e2ae529c6206.png)


Twin's desired プロパティを受信しているセルを確認すると、受信したことを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/3d7334f6-afef-4a3b-aef9-b733f62bca61.png)


Azure IoT Hub のポータル画面にて、クエリを発行すると同じ値が設定されていることを確認で

```sql
SELECT * FROM devices WHERE deviceId = 'qiita_test_01'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/8a67fc4c-7fd9-494c-b5c4-71874df6c8f6.png)