Overview
Device Shadows provide a virtual representation of each device in the cloud. They enable bidirectional state synchronization — the cloud sets a desired state, the device reports its reported state, and EdgeFlow automatically calculates the delta (differences that need to be resolved).
This pattern (inspired by AWS IoT Device Shadows) allows you to manage device configuration even when devices are temporarily offline. When a device reconnects, it syncs with its shadow and applies any pending changes.
Shadow Document Structure
{
"device_id": "dev_abc123xyz",
"version": 42,
"desired": {
"flow_autostart": true,
"log_level": "info",
"update_channel": "stable",
"gpio_config": {
"pin_17": "output",
"pin_27": "input"
}
},
"reported": {
"flow_autostart": true,
"log_level": "debug",
"firmware_version": "1.2.3",
"uptime": 86400,
"gpio_config": {
"pin_17": "output",
"pin_27": "input"
}
},
"delta": {
"log_level": "info"
},
"metadata": {
"desired": {
"log_level": { "timestamp": "2026-02-21T12:00:00Z" }
},
"reported": {
"log_level": { "timestamp": "2026-02-21T10:00:00Z" }
}
},
"updated_at": "2026-02-21T12:00:00Z"
} State Types
| State | Direction | Description |
|---|---|---|
| Desired | Cloud → Device | The configuration the cloud wants the device to have. Set by operators or automated policies. |
| Reported | Device → Cloud | The actual current state of the device. Updated by the device after applying changes. |
| Delta | Computed | Fields where desired differs from reported. The device should resolve these differences. |
Synchronization Flow
Cloud Device
│ │
│ 1. Set desired state │
│ PUT /devices/{id}/shadow │
│─────────────────────────────>│
│ │
│ 2. Device fetches shadow │
│ GET /devices/{id}/shadow │
│<─────────────────────────────│
│ │
│ 3. Device detects delta │
│ (desired != reported) │
│ │
│ 4. Device applies changes │
│ and updates reported │
│ PUT /devices/{id}/shadow │
│<─────────────────────────────│
│ │
│ 5. Delta becomes empty │
│ (desired == reported) │
│ │ Shadow Manager
The Shadow Manager runs on each device and handles all shadow operations. It provides:
- GetShadow() — Fetch the full shadow document from the cloud
- UpdateReported(state) — Push the device's current state to the cloud
- GetCurrentShadow() — Return the locally cached shadow (no network call)
- GetDelta() — Calculate differences between desired and reported states
- SetDesiredChangeHandler(callback) — Register a callback for cloud-side state changes
- StartPeriodicSync(interval) — Automatic background sync (default: every 5 minutes)
Periodic Sync
After the initial connection, the Shadow Manager automatically syncs with the cloud every 5 minutes. During each sync cycle:
- Fetch the latest shadow from the cloud
- Compare with the locally cached version
- If the desired state changed, trigger the change handler callback
- Update the local cache
Version Conflict Detection
Each shadow document has a version number that increments with every update.
This prevents stale updates from overwriting newer state. If a version conflict occurs,
the device re-fetches the latest shadow before retrying.
Common Use Cases
| Use Case | Desired State | Reported State |
|---|---|---|
| Change log level | {"log_level": "debug"} | {"log_level": "info"} → {"log_level": "debug"} |
| Enable flow autostart | {"flow_autostart": true} | {"flow_autostart": true} |
| Update firmware | {"target_version": "1.3.0"} | {"firmware_version": "1.2.3"} |
| Configure GPIO | {"gpio": {"pin_17": "output"}} | {"gpio": {"pin_17": "output"}} |
API Reference
Cloud API
# Get device shadow
GET /api/v1/devices/{deviceId}/shadow
Header: X-API-Key: efk_...
# Update device shadow (reported state)
PUT /api/v1/devices/{deviceId}/shadow
Header: X-API-Key: efk_...
Body: {"reported": {"log_level": "debug", "uptime": 86400}} Local Device API (via Tunnel Command)
# Get shadow command
{
"type": "command",
"action": "get_shadow",
"payload": {}
}
# Update desired state command
{
"type": "command",
"action": "update_desired",
"payload": {
"desired": {"log_level": "info"}
}
}