Skip to main content

Device Shadows

AWS IoT-style device shadow implementation for EdgeFlow. Synchronize desired and reported state between cloud and device with automatic delta detection.

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:

  1. Fetch the latest shadow from the cloud
  2. Compare with the locally cached version
  3. If the desired state changed, trigger the change handler callback
  4. 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"}
  }
}