AWS IoT Core の シャドウ機能についての調査まとめ

## シャドウ概要

AWS IoT Coreでは、ラズパイなどのエッジ端末をモノ(Thing)として登録し、その状態を保持する機能を持っています。この「状態(state)」がシャドウ(shadow)です。

アプリケーションでは、この状態の変更を通してデバイスを操作します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696083/08583dbd-5b2b-147a-2088-7ffe4a445ad3.png)

状態には**”reported”**、**”desired”**の2つの要素が存在します。

- **”desired”**： アプリケーションとして期待する状態を示すのに使用します。アプリケーションで状態の変更要求を出す場合は、この状態を更新します。
- **“reported”**： デバイス自身がどういった状態なのかを示すのに使用します。この状態を変更するのは基本的にはデバイス自身です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696083/434733f3-54fe-7490-d770-100512a69f6c.png)

## 具体例

下記例は、AWSコンソールからシャドウを追加し、シャドウの内容を確認した結果です。



1. IoT Core > [モノ]タブ  > 表示されたモノを選択 > [シャドウ]タブ > シャドウを追加するボタン押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696083/6df853af-4ad1-681d-1e08-a0f5368ff828.png)

2. シャドウ名の入力欄が表示されるので、任意(今回は「test」)で入力し、シャドウを追加する。

3. 追加されたシャドウを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696083/a1e4f6cc-24d1-e0cf-37e6-034ffd601ca6.png)

4. 下記の通り、前述したdesired、reportedの状態が表示されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696083/c1268888-0e13-af8f-997f-d42c904f8e93.png)

5. アプリケーションからの状態変更があったと想定して、コンソールでdesired側の"welcome"の値を"aiueo"変更すると、シャドウステータスが下記のように変化する。

   **delta**とは、状態に差異がある箇所を抽出し、表示してくれるもの

   ```
    {
      "desired": {
        "welcome": "aiueo"
      },
      "reported": {
        "welcome": "aws-iot"
      },
      "delta": {
        "welcome": "aiueo"
      }
    }
   ```

6. 次にエッジから状態変更があったと想定して、コンソールでreportedの"welcome"の値を"oeuia"変更すると、シャドウステータスが下記のように変化する。

   まだ状態が一致していないため、deltaは表示されたままとなる。

   ```
   {
     "desired": {
       "welcome": "aiueo"
     },
     "reported": {
       "welcome": "oeiua"
     },
     "delta": {
       "welcome": "aiueo"
     }
   }
   ```

7. 最後に、コンソールでreportedの"welcome"の値を"aiueo"変更し状態を一致させると、シャドウステータスが下記のように変化する。

   状態が一致するとdeltaが消える。

   ```
   {
     "desired": {
       "welcome": "aiueo"
     },
     "reported": {
       "welcome": "aiueo"
     }
   }
   ```

状態はjson形式で管理されるため、例えば下記のように、複数の情報を持つことができ、また配列なども保持することが可能。

そのため、例えば、1つのシャドウで1つのエッジにぶら下がっている複数のデバイス(照明やエアコンなど)の複数の状態(電源ON/OFF、温度など)を管理することも可能です。
(複数のシャドウで管理することも可能ですが、１つのシャドウで管理することもできます。という意味です。)

```
  "desired": {
    "welcome": "aws-iot",
    "device": [
      {
        "type": "light",
        "id": "1",
        "connect": true,
        "power": "on"
      },
      {
        "type": "aircon",
        "id": "2",
        "connect": true,
        "ondo": "20"
      }
    ]
  },
```

## シャドウの種類

### 名前なしシャドウ

前述の例では、シャドウに名前を決めていたが、名前なしでシャドウを作ることもできる。

AWSコンソールで作成する場合、前述の例の名前決定箇所で名前を入力しなければ生成される。

[公式サイト](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-device-shadows.html#device-shadow-using)によると、あらかじめ1つしかシャドウを利用しないことが決まっているのであれば、名前なしシャドウを利用すると良いらしいが、既に複数のシャドウを使用する可能性があるのであれば、後述する名前付きシャドウを利用した方が良いとのこと。

拡張性を考えるなら、名前付きシャドウを使う方が良いとの理解。



[メリット]

- 名前が無いため、利用する側(エッジやLambda)が名前を知らなくてもアクセスできる。(単純)

[デメリット]

- 名前がないため、1つしか作ることができない。

  

### 名前付きシャドウ

前述の例で記載した通り、名前を指定したシャドウ。

[メリット]

- シャドウ内で複数の状態(例えば、電源ON/OFF、温度など)を管理することもできるが、複数の名前付きシャドウを作成し、個々に管理することが可能。



[デメリット]

- 基本的(*)にはエッジ端末が直接IoT Coreからシャドウの一覧を取得することはできないため、あらかじめ名前は決めておく必要がある。

(*)Sigv4(IAMユーザのアクセスキーとシークレットキーがあれば、取得可能だが、クライアント証明書では取得することができなかった)



## シャドウへのアクセス方法

シャドウへの制御は、名前付きシャドウ と 名前なしシャドウでそれぞれ異なり、それぞれで、

GET、UPDATE、DELETEの3種類の制御が可能。

- GETは、指定したシャドウの状態を取得

- UPDATEは、指定したシャドウの状態を更新(desired、reportedのどちらを更新するか選択できる)

- DELETEは、指定したシャドウの削除



### エッジ側からのアクセス方法

<余談>AWS のnodejsのサンプルコードにシャドウに関するサンプルはなかったため、pythonのサンプルを元にnodejsに実装して確認してみた。
前述の通り、シャドウの制御は３つしかないので比較的簡単に実装できる。

AWS SDKの"aws-iot-device-sdk-v2"をインポートするとiotshadowというオブジェクトがある。

そのオブジェクトの提供関数に、シャドウの名前付き、名前なしでそれぞれGET、UPDATE、DELETEの関数が提供されているため、それを用いる。

なお、指定したシャドウの状態が変化した場合にコールバック関数を登録できる関数もある。



```
//シャドウクライアント生成。connectionはMQTTのコネクションオブジェクト
let shadow = new iotshadow.IotShadowClient(this.connection);

// GET関数コール
const getNamShadow: iotshadow.model.GetNamedShadowRequest = {
thingName: client_id,								//モノの名前
shadowName: shadowName,								//シャドウの名前
};
await shadow.publishGetNamedShadow(getNamShadow, mqtt.QoS.AtLeastOnce);
```



### AWS側からのアクセス方法

Lambda経由でIoT CoreにMQTTメッセージを送信することで行う。

IoT CoreへMQTTメッセージを送信するSDK(boto3)が提供されているため、それを用いる。

GET、UPDATE、DELETE制御は、それぞれTopicを指定し行う。

名前付きシャドウの場合、下記のようにTopicにモノの名前、シャドウ名を含めMQTTメッセージを送信し、受信を待つ。

```
iot = boto3.client('iot-data')
topic = f'$aws/things/{thingName}/shadow/name/{shadowName}/update'
response = iot.get_thing_shadow(thingName=thingName, shadowName=shadowName)
```

Topicについては、[公式サイト](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/device-shadow-mqtt.html)参照

## 補足

シャドウをあらかじめ生成していなくても、UPDATE時になければ新規生成される。

## 最後に
シャドウの状態は、AWS IoT Coreで管理されるため、例えばエッジの電源が落ちていても状態は残り続けます。
そのため、エッジ端末が起動していなかったとしても、シャドウの更新をしておけば、エッジ起動時にシャドウの状態に合わせて、更新後の状態にエッジを動作させる。といったことが可能になります。
シャドウが無いとエッジの状態を管理するための状態をストレージに持つ必要があるので、シャドウはとても便利に思いました。

### 参考URL

・概要

https://hub.appirio.jp/tech-blog/device-management-through-aws-iot-shadow

https://dev.classmethod.jp/articles/aws-iot-things-shadow/

・シャドウ動かしてみた

https://iotnews.jp/archives/6599

・シャドウサンプル

https://dev.classmethod.jp/articles/aws-iot-things-shadow/

・公式サイト

https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/device-shadow-mqtt.html#update-accepted-pub-sub-topic