# Device Shadow

Device Shadowは、EMQX Fleetsがクラウドの意図とデバイスの現実を同期させるために使用する仕組みです。デバイスがオフラインでも常に利用可能な、デバイスの状態を永続的かつバージョン管理された形で表現します。

## 3つの状態モデル

各Thingは、3つの状態を含む1つのシャドウドキュメントを持ちます。

| 状態 | 所有者 | 意味 |
|---|---|---|
| **Reported** | デバイス | デバイス自身が最後に報告した実際の現在の状態 |
| **Desired** | クラウド | クラウドがデバイスに到達してほしい目標状態 |
| **Delta** | Fleetsによる計算 | DesiredとReportedの差分、つまりデバイスがまだ適用すべき内容 |

クラウドがDesired状態を書き込むと、FleetsがDeltaを計算してデバイスにプッシュします。デバイスはDeltaを適用し、新しい状態を報告します。Fleetsはシャドウを更新し、Deltaを再計算します（DesiredとReportedが一致するとDeltaは空になります）。

```text
クラウドがDesiredを書き込む  →  FleetsがDeltaを計算  →  デバイスがDeltaを受信
                                                            ↓
クラウドがReportedを読み取る  ←  FleetsがReportedを保存  ←  デバイスが状態を報告
```

## シャドウのバージョニング

各シャドウドキュメントには単調増加するバージョン番号があります。DesiredまたはReportedの状態が変わるたびにバージョンがインクリメントされます。デバイスはこのバージョンを使って古い更新を検出し、順序が逆の変更を適用しないようにできます。

## コンソールでシャドウを確認する

1. Fleetsのデプロイメントで、左メニューの**Shadow Sync**に移動します。
2. 左側のリストからThingを選択します。各エントリにはThing名とそのMQTTクライアントIDが表示されます。
3. 右側のパネルには以下が表示されます：
   - **Thing ID**、**Thing Type**、現在の**Version**、および**Updated At**のタイムスタンプ
   - **Current state (Reported)**：デバイスが最後に報告した状態
   - **Target state (Desired)**：クラウドがデバイスに到達してほしい状態

デバイスがまだ状態を報告していない場合、Reportedパネルには「Device has not reported any state yet.」と表示されます。

最新のシャドウデータを再読み込みするには、**Refresh**（リフレッシュアイコン）をクリックしてください。

![shadow_sync](./_assets/shadow_sync.png)

## Desired状態を更新する

デバイスにDesired状態の変更をプッシュするには：

1. **Shadow Sync**に移動し、Thingを選択します。
2. **Target state (Desired)**パネルにJSONオブジェクト形式でDesired状態を入力します：
   ```json
   {
     "targetTemperature": 22,
     "mode": "cooling"
   }
   ```
3. **Update Desired**をクリックします。

FleetsはDesired状態を保存し、Deltaを計算してMQTT経由でデバイスにパブリッシュします。

REST APIを使ってプログラムからDesired状態を更新することも可能です：
```text
PATCH /api/v1/things/{id}/shadow
```

## デバイスが状態変更を受け取る方法

クラウドがDesired状態を更新すると、Fleetsは2つのMQTTトピックにパブリッシュします：

| トピック | 内容 |
|---|---|
| `$emqx/things/{thingName}/shadow/update` | Desired状態の全ペイロード |
| `$emqx/things/{thingName}/shadow/update/delta` | Reportedと異なるフィールドのみ |

デバイスはDeltaトピックをサブスクライブし、異なるフィールドのみを適用してから更新された状態を報告するべきです。

デバイスがDesired状態更新時にオフラインの場合、Deltaはデバイスが再接続してトピックをサブスクライブした際に配信されます（永続的なMQTTセッションを使用）。

## デバイスが状態を報告する方法

デバイスは現在の状態を以下のトピックにパブリッシュして報告します：
```text
$emqx/things/{thingName}/shadow/update
```

ペイロード例：
```json
{
  "state": {
    "reported": {
      "temperature": 21.5,
      "targetTemperature": 22,
      "mode": "cooling"
    }
  },
  "clientToken": "optional-correlation-id",
  "version": 3
}
```

EMQXはこのメッセージをFleetsにルーティングし、Fleetsは以下を行います：
1. GreptimeDB（時系列履歴）とPostgreSQL（最新コピー）にReported状態を保存
2. Deltaを再計算
3. シャドウのバージョンを更新

また、デバイスはHTTPS経由で状態を報告することも可能です：
```text
POST /api/v1/thing-datas/shadow/reported
```

プロトコルの詳細は[MQTT Integration](./device_integration/mqtt_integration.md)または[HTTPS Integration](./device_integration/https_integration.md)を参照してください。

## Desired状態をクリアする

Desired状態を削除するには（例えばデバイスが収束した後）：

1. **Shadow Sync**に移動し、Thingを選択します。
2. **Clear Desired**（リフレッシュボタンの隣のアイコン）をクリックします。

REST APIを使う場合は以下を実行します：
```text
DELETE /api/v1/things/{id}/shadow
```

## シャドウ履歴

Reported状態はGreptimeDBに時系列データとして保存されます。過去のシャドウ値は[Device Query](./device_query.md)の時系列フィルターで`tsFrom`、`tsTo`、`tsFieldName`パラメータを使ってクエリ可能です。

## 次のステップ

- [Device Query](./device_query.md)
- [MQTT経由でデバイスを接続する](./device_integration/mqtt_integration.md)
- [デバイス統合の概要](./device_integration/device_integration_overview.md)
