クラウド・プラットフォーム

デバイスシャドウ

機器の状態をクラウド側に保持する仕組み。

概要

デバイスシャドウ(Device Shadow)とは、実際の物理デバイスの状態をクラウド側にJSONドキュメントとして複製・保持する仕組みです。デバイスがオフラインの間もクラウドはデバイスの最後に報告された状態を記憶し、アプリケーションはデバイスの接続状態に関わらず状態を読み書きできます。デバイスが再接続すると、クラウドが保持していた「あるべき姿(desired)」と「実際の状態(reported)」の差分が同期されます。

デバイスシャドウの概念は、IoTシステムにおける根本的な課題、すなわち「デバイスは常にオンラインではない」という問題を解決します。電池駆動のセンサーが数分に1回だけ接続するような用途でも、アプリケーション側からは常に最新の状態にアクセスできるように見えます。

AWS IoT Coreでは「Device Shadow」、Azure IoT Hubでは「デバイスツイン(Device Twin)」と呼ばれますが、基本的なコンセプトは共通しています。オープンソース実装としてはEclipse Dittoが有名です。

主な用途は以下の通りです。

  • オフラインデバイスへの設定変更: デバイスが接続したタイミングで新しい設定を取得させる
  • 最終状態の確認: デバイスが今どの設定で動いているかをいつでも照会できる
  • 非同期コマンド: デバイスのオンライン/オフラインに関わらず命令を予約できる
  • アプリケーションとデバイスの疎結合: 接続タイミングの同期が不要になる

歴史・背景

デバイスシャドウの概念が広く普及したのは2015年のAWS IoTサービス開始がきっかけです。それ以前も「デジタルレプリカ」や「状態キャッシュ」という考え方は存在していましたが、統一されたAPI・プロトコルとして標準化されたのはAWSが大きな役割を果たしました。

Microsoftは同様の機能を「デバイスツイン」と命名し、AzureのIoTサービスに組み込みました。デバイスツインはシャドウの概念をさらに拡張し、「タグ」というクラウド専用の属性(デバイスからは読めないメタデータ)を追加しています。これにより、デバイスの位置情報やグループ情報といった管理情報をデバイスツインで一元管理できます。

2020年代に入ると、デバイスシャドウはデジタルツイン概念の基礎的な実装として位置づけられるようになりました。デジタルツインはシャドウをさらに発展させ、物理デバイスの振る舞いをシミュレーションする機能まで含む上位概念です。

技術仕様

Device Shadow のデータ構造(AWS IoT Core)

{
  "state": {
    "desired": {
      "led": "on",
      "target_temp": 22.0,
      "sampling_interval_sec": 30
    },
    "reported": {
      "led": "off",
      "current_temp": 21.5,
      "sampling_interval_sec": 60,
      "firmware_version": "1.2.3",
      "battery_percent": 87
    },
    "delta": {
      "led": "on",
      "target_temp": 22.0,
      "sampling_interval_sec": 30
    }
  },
  "metadata": {
    "desired": {
      "led": {"timestamp": 1700001000},
      "target_temp": {"timestamp": 1700001000}
    },
    "reported": {
      "led": {"timestamp": 1700000500},
      "current_temp": {"timestamp": 1700000500}
    }
  },
  "version": 15,
  "timestamp": 1700001000
}

各フィールドの役割

フィールド書き込み元読み取り元説明
desiredクラウド(アプリ)デバイスデバイスに実現させたい状態
reportedデバイスクラウド(アプリ)デバイスが実際に報告した現在状態
deltaシステム自動生成デバイスdesiredとreportedの差分(自動計算)
metadataシステム自動生成両者各フィールドの最終更新タイムスタンプ
versionシステム自動採番両者楽観的ロック用バージョン番号

MQTTトピック(AWS IoT Core)

# デバイスシャドウ操作用のシステムトピック
$aws/things/{thing-name}/shadow/update          # Shadow更新リクエスト
$aws/things/{thing-name}/shadow/update/accepted # 更新成功通知
$aws/things/{thing-name}/shadow/update/rejected # 更新失敗通知
$aws/things/{thing-name}/shadow/update/delta    # delta通知(desiredとreportedの差分)
$aws/things/{thing-name}/shadow/get             # Shadow取得リクエスト
$aws/things/{thing-name}/shadow/get/accepted    # 取得成功(Shadowドキュメント全体)
$aws/things/{thing-name}/shadow/delete          # Shadow削除

名前付きシャドウ(Named Shadow)

AWS IoT Coreでは、1つのデバイスに複数のシャドウを持たせることができます。用途別にシャドウを分割することで、更新頻度の高い状態と低い状態を分離でき、システムの複雑さを管理できます。

$aws/things/{thing-name}/shadow/name/{shadow-name}/update

例:

  • telemetry: センサー値(頻繁に更新)
  • config: 設定値(滅多に変わらない)
  • ota: ファームウェア更新状態

動作原理

オフラインデバイスへのコマンド配信フロー

1. デバイスA がオフライン状態

2. オペレーターがクラウドから設定変更
   → Shadow の "desired" が更新される
   {"desired": {"led": "on", "sampling_interval_sec": 10}}

3. デバイスA がオンラインになる
   → $aws/things/deviceA/shadow/update/delta を受信
   {"state": {"led": "on", "sampling_interval_sec": 10}}

4. デバイスA が変更を適用し、reportedを更新
   → Shadow に reported を送信
   {"reported": {"led": "on", "sampling_interval_sec": 10}}

5. delta が消える(desired == reported)
   → 同期完了

楽観的ロック(バージョン管理)

競合する更新を防ぐために、シャドウにはバージョン番号が付与されます。古いバージョンに基づく更新は拒否されます。

# 楽観的ロックを使ったShadow更新
import boto3
import json

client = boto3.client('iot-data', region_name='ap-northeast-1')

# 現在のShadowを取得
response = client.get_thing_shadow(thingName='device-001')
shadow = json.loads(response['payload'].read())
current_version = shadow['version']

# バージョン指定で更新(競合があれば409エラー)
try:
    client.update_thing_shadow(
        thingName='device-001',
        payload=json.dumps({
            "state": {
                "desired": {"led": "on"}
            },
            "version": current_version  # 現在のバージョンを指定
        })
    )
    print("Shadow更新成功")
except client.exceptions.ConflictException:
    print("バージョン競合: 再取得して再試行")

デバイス側での差分処理

#include "aws_iot_shadow_interface.h"

// Shadow deltaコールバック
void shadow_delta_callback(const char *thing_name, const char *delta_document,
                            uint32_t document_length, jsonStruct_t *js_handle) {
    // deltaドキュメントをパース
    char led_state[8] = {0};
    int32_t sampling_interval = 0;

    jsonStruct_t led_handler = {
        .cb = NULL,
        .pData = led_state,
        .pKey = "led",
        .type = SHADOW_JSON_STRING,
        .dataLength = sizeof(led_state)
    };

    // deltaからledの値を取得
    if (aws_iot_shadow_json_extract_key_value(delta_document, "led", &led_handler) == SUCCESS) {
        if (strcmp(led_state, "on") == 0) {
            gpio_set(LED_PIN, GPIO_HIGH);
        } else {
            gpio_set(LED_PIN, GPIO_LOW);
        }
    }

    // 変更を適用後、reportedを更新
    jsonStruct_t reported_led = {
        .cb = NULL,
        .pData = led_state,
        .pKey = "led",
        .type = SHADOW_JSON_STRING,
        .dataLength = sizeof(led_state)
    };
    aws_iot_shadow_update(shadow_client, thing_name, &reported_led, 1,
                          shadow_update_callback, NULL, 4, false);
}

用途・ユースケース

スマートホームデバイス制御

スマート照明、エアコン、鍵などを「desired」に書き込むことで、デバイスのオンライン/オフライン状態に関わらず制御命令を予約。帰宅前にエアコンをオンにする操作がスマートフォンアプリから可能になります。

工場設備の設定管理

多数の製造設備の動作パラメータをShadowで一元管理。生産計画に合わせてラインの速度設定を変更する際、オペレーターは各機械に個別にアクセスする必要なく、クラウドから一括設定変更が可能です。

フィールド機器のOTA管理

ファームウェアバージョンをShadowで追跡し、更新が必要なデバイスを特定。desiredに新バージョン番号を設定し、デバイスが接続した際に自動的にOTA更新を開始させます。

# フリート全体のファームウェアを一括更新(AWS IoT Fleet Indexing使用)
import boto3

iot_client = boto3.client('iot', region_name='ap-northeast-1')
iotdata_client = boto3.client('iot-data', region_name='ap-northeast-1')

# 古いファームウェアのデバイスを検索
response = iot_client.search_index(
    queryString='shadow.reported.firmware_version:1.2.0'
)

for thing in response['things']:
    # 各デバイスのShadowにdesiredファームウェアバージョンを設定
    iotdata_client.update_thing_shadow(
        thingName=thing['thingName'],
        payload='{"state":{"desired":{"firmware_version":"1.3.0","ota_url":"https://firmware.example.com/v1.3.0.bin"}}}'
    )
    print(f"{thing['thingName']} にOTA更新を予約")

実装・開発のポイント

Shadow設計のベストプラクティス

  1. 更新頻度で分割: 高頻度なテレメトリと低頻度な設定を別Shadowに
  2. 最小限のデータ: Shadowはドキュメント全体を都度送信するため、サイズを小さく保つ
  3. 冪等性を保証: 同じdesiredが何度来ても同じ結果になるようにデバイス側を実装
  4. タイムアウト設計: デバイスがdesiredに応答しない場合のフォールバック処理を実装

エラーハンドリング

// Shadow更新の失敗をハンドリング
void shadow_update_callback(const char *thing_name, ShadowActions_t action,
                             Shadow_Ack_Status_t status, const char *received_json_document,
                             void *p_context_data) {
    if (SHADOW_ACK_ACCEPTED == status) {
        printf("Shadow更新成功\n");
    } else if (SHADOW_ACK_TIMEOUT == status) {
        printf("Shadow更新タイムアウト - 次回接続時に再試行\n");
        // ローカルにpendingフラグを保存
        save_pending_update_flag();
    } else if (SHADOW_ACK_REJECTED == status) {
        printf("Shadow更新拒否: %s\n", received_json_document);
        // バージョン競合の場合はShadowを再取得
    }
}

他技術との比較

項目Device Shadow(AWS)デバイスツイン(Azure)Eclipse Ditto(OSS)
提供形態マネージドマネージドセルフホスト
データ形式JSONJSONJSON
メタデータ(タグ)なし(グループで代替)あり(タグ機能)あり
名前付きシャドウありなし(単一ツイン)あり
バージョン管理ありあり(etag)あり
最大サイズ8 KB8 KB設定次第
オープンソースなしなしApache 2.0

デバイスシャドウはデジタルツインの基礎となる概念であり、AWS IoT CoreAzure IoT Hubの中核機能として組み込まれています。フリート管理においては、シャドウを活用したデバイスの状態一括監視が基本的なパターンとなっています。

関連用語

参考リンク