# Device Shadow Sync

This page describes the shadow synchronization flow from the device's perspective, covering startup, receiving desired state changes, and reporting state.

## Overview

Shadow sync ensures the device converges toward the cloud-defined desired state, even across disconnections. The flow has three main phases:

1. **Startup**: fetch the current shadow to detect any pending desired state
2. **Receive delta**: apply changes pushed by the cloud
3. **Report state**: confirm the device's current state back to Fleets

## Startup Flow

When the device connects (or reconnects), it should fetch the current shadow before entering normal operation. This catches any desired state changes that were made while the device was offline.

```text
Device connects to EMQX Broker
        │
        ▼
Subscribe to shadow and command topics
        │
        ▼
GET /api/v1/thing-datas/{thingName}/shadow   (HTTPS)
        │
        ▼
Is delta non-empty?
   YES → Apply desired values to device
       → Report new state (see Reporting State below)
    NO → Continue normal operation
```

## Receive a Delta

When the cloud updates the desired state, Fleets publishes to:

```text
$emqx/things/{thingName}/shadow/update/delta
```

The delta payload contains only the fields where desired and reported differ:

```json
{
  "version": 5,
  "timestamp": 1748390400,
  "state": {
    "targetTemperature": 24
  },
  "metadata": {
    "targetTemperature": { "timestamp": 1748390400 }
  }
}
```

Device behavior:

```text
Receive delta message
        │
        ▼
Extract changed fields from state
        │
        ▼
Apply changes to device hardware/config
        │
        ▼
Report updated state (see Reporting State)
```

Do not echo the entire desired state back as reported. Report the device's actual current state, which may differ from the desired state if the device cannot fully apply it (for example, hardware limits).

## Report State

Report the device's current state by publishing to the shadow update topic (QoS 1):

```text
Topic:   $emqx/things/{thingName}/shadow/update
QoS:     1
Retain:  false
```

Payload:
```json
{
  "state": {
    "reported": {
      "temperature": 21.5,
      "targetTemperature": 24,
      "mode": "cooling"
    }
  },
  "clientToken": "tok-001",
  "version": 4
}
```

After Fleets processes the report:
- The reported state is stored in GreptimeDB and PostgreSQL.
- The delta is recomputed. If reported now matches desired, the delta becomes empty.
- The shadow version increments.

## What Happens When Desired Matches Reported

Once the device's reported state matches the cloud's desired state, the delta becomes an empty object (`{}`). No further delta messages are published until the desired state changes again.

## Full Shadow State Diagram

```text
Cloud writes desired state
        │
        ▼
Fleets computes delta = desired − reported
        │
        ▼
Fleets publishes delta to device via MQTT
        │
        ▼
Device receives delta → applies changes
        │
        ▼
Device publishes reported state
        │
        ▼
Fleets stores reported state
Fleets recomputes delta (may become empty)
```

## Offline Periods

If the device uses a **persistent MQTT session** (`clean session = false`), the broker queues delta messages published while the device was offline. On reconnect, the device receives the queued messages before subscribing to new ones.

Even with a persistent session, run the startup GET to ensure you have the full current shadow, because a sequence of rapid desired-state updates may result in only the final delta being queued.

## Version Conflicts

If you include a `version` field in the reported payload and it does not match the server's current version, Fleets rejects the update with an error response. To avoid this, either omit the `version` field or fetch the current shadow before reporting.

## Next Steps

- [MQTT Integration](./mqtt_integration.md)
- [HTTPS Integration](./https_integration.md)
- [Device Shadow](../device_shadow.md)
