From 12c6748a8a84e54ce603c98ad9509e4964d88c88 Mon Sep 17 00:00:00 2001 From: majabirmancevic Date: Thu, 23 Jul 2026 15:27:02 +0200 Subject: [PATCH] MF-61- Add device shadow service docs --- docs/architecture.md | 5 +- docs/shadows.md | 72 +++++++++++++++++++++++++++++ docs/swagger/swagger-initializer.js | 1 + mkdocs.yml | 1 + 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 docs/shadows.md diff --git a/docs/architecture.md b/docs/architecture.md index 8f2c02c..1278bbd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -5,7 +5,7 @@ Mainflux IoT platform is comprised of the following services: | Service | Description | -|:-----------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------| +| :--------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | | [auth](https://github.com/MainfluxLabs/mainflux/tree/master/auth) | Manages platform's orgs and auth concerns | | [users](https://github.com/MainfluxLabs/mainflux/tree/master/users) | Manages platform's users and auth concerns | | [things](https://github.com/MainfluxLabs/mainflux/tree/master/things) | Manages platform's things, profiles, groups and group members | @@ -18,6 +18,7 @@ Mainflux IoT platform is comprised of the following services: | [rules](https://github.com/MainfluxLabs/mainflux/tree/master/rules) | Evaluates threshold rules and Lua scripts against incoming device messages | | [alarms](https://github.com/MainfluxLabs/mainflux/tree/master/consumers/alarms) | Persists alarms triggered by the rules engine or Lua scripts to PostgreSQL | | [downlinks](https://github.com/MainfluxLabs/mainflux/tree/master/downlinks) | Manages scheduled outbound HTTP requests (downlinks) for things and groups | +| [shadows](https://github.com/MainfluxLabs/mainflux/tree/master/shadows) | Maintains a persisted desired/reported state record (shadow) for each thing | | [webhooks](https://github.com/MainfluxLabs/mainflux/tree/master/webhooks) | Forwards device messages to external HTTP endpoints | | [smtp-notifier](https://github.com/MainfluxLabs/mainflux/tree/master/cmd/smtp-notifier) | Sends email notifications to contacts defined on notifier records | | [smpp-notifier](https://github.com/MainfluxLabs/mainflux/tree/master/cmd/smpp-notifier) | Sends SMS notifications to contacts defined on notifier records | @@ -52,7 +53,6 @@ things and profiles) in CRUD fashion and define access control. `Thing` represents devices (or applications) connected to Mainflux that uses the platform for message exchange with other "things". - ## Messaging Mainflux uses [NATS](https://nats.io) as its messaging backbone, due to its @@ -65,6 +65,7 @@ However, in order to be post-processed and normalized, messages should be formatted using [SenML](https://tools.ietf.org/html/draft-ietf-core-senml-08). ## Unified IoT Platform + Running Mainflux on gateway moves computation from cloud towards the edge thus decentralizing IoT system. Since we can deploy same Mainflux code on gateway and in the cloud there are many benefits but the biggest one is easy deployment and adoption - once the engineers understand how to deploy and maintain the platform, they will have the same known work across the whole edge-fog-cloud continuum. Same set of tools can be used, same patches and bug fixes can be applied. The whole system is much easier to reason about, and the maintenance is much easier and less costly. diff --git a/docs/shadows.md b/docs/shadows.md new file mode 100644 index 0000000..9e84b81 --- /dev/null +++ b/docs/shadows.md @@ -0,0 +1,72 @@ +# Shadows + +Shadows service maintains a **device shadow** for each thing — a persisted record holding the thing's last _reported_ state and its _desired_ state. It lets users read and set a thing's state even while the thing is offline, and aligns the two whenever the thing reconnects. + +A shadow is a single record per thing. + +| Field | Description | +| ------------- | ------------------------------------------------------------------------------- | +| `thing_id` | ID of the thing the shadow belongs to | +| `state` | Nested object holding the `desired`, `reported`, and `delta` states (see below) | +| `reported_at` | Unix timestamp (seconds) of the last reported-state update | +| `updated_at` | Unix timestamp (seconds) of the last desired-state update | + +## State + +`desired`, `reported`, and `delta` are each a free-form JSON object (a set of key/value pairs). + +| Field | Description | +| ---------- | -------------------------------------------------------------------------------------------------------- | +| `desired` | State the application wants the thing to reach. | +| `reported` | State the thing last reported. Merged from the thing's telemetry messages. | +| `delta` | Computed subset of `desired` whose values differ from (or are absent in) `reported`. Omitted when empty. | + +The `delta` is derived on read and on every state change; it is never stored directly. Keys present only in `reported` are not part of the delta. + +## How it works + +- **Desired state** is set by a user through the HTTP API. On update, the service recomputes the delta and publishes it to the thing on its command subject (`things..commands.shadow`). +- **Reported state** is updated automatically as the thing publishes messages. The service consumes messages from the broker, flattens each into a state patch, and merges the patch into `reported` (no-op writes are skipped). +- On each reported-state change, any still-pending delta is re-published, so a reconnecting thing receives commands it missed while offline. + +Authorization is delegated to the Things service: reading a shadow requires `viewer` access on the thing's group, while updating or removing a shadow requires `editor` access. + +## Managing a shadow + +```bash +# Set the desired state for a thing +curl -s -S -i -X PUT \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "desired": {"fanSpeed": 3, "targetTemp": 21} + }' \ + https://localhost/svcshadows/things//shadows +``` + +```json +{ + "thing_id": "111e4567-e89b-12d3-a456-426614174000", + "state": { + "desired": { "fanSpeed": 3, "targetTemp": 21 }, + "reported": { "fanSpeed": 1, "targetTemp": 19 }, + "delta": { "fanSpeed": 3, "targetTemp": 21 } + }, + "reported_at": 1774342400, + "updated_at": 1774342578 +} +``` + +```bash +# View a thing's shadow +curl -s -S -i \ + -H "Authorization: Bearer " \ + https://localhost/svcshadows/things//shadows + +# Remove a thing's shadow +curl -s -S -i -X DELETE \ + -H "Authorization: Bearer " \ + https://localhost/svcshadows/things//shadows +``` + +For the full API reference, see the [API documentation](https://mainfluxlabs.github.io/docs/swagger/). diff --git a/docs/swagger/swagger-initializer.js b/docs/swagger/swagger-initializer.js index b700f13..118eddf 100644 --- a/docs/swagger/swagger-initializer.js +++ b/docs/swagger/swagger-initializer.js @@ -16,6 +16,7 @@ window.onload = function() { { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/notifiers.yml", name: "notifiers service" }, { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/readers.yml", name: "readers service" }, { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/rules.yml", name: "rules service" }, + { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/shadows.yml", name: "shadows service" }, { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/things.yml", name: "things service" }, { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/uiconfigs.yml", name: "uiconfigs service" }, { url: "https://raw.githubusercontent.com/MainfluxLabs/mainflux/refs/heads/master/api/openapi/users.yml", name: "users service" }, diff --git a/mkdocs.yml b/mkdocs.yml index 322418d..11802e5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ nav: - Storage: storage.md - Rules: rules.md - Alarms: alarms.md + - Shadows: shadows.md - Converters: converters.md - Filestore: filestore.md - UIConfigs: uiconfigs.md