Skip to content

Device data and control contract #

This page is the low-level device-side integration reference between your product application and the Dataplicity field agent. It intentionally contains release-specific details that do not belong in the higher-level production behaviour and design boundaries guide.

For lasting settings delivery, qualify field agent 0.1.52+ or a later release that documents the same local settings consumer. For streams and actions, verify the exact agent release you ship on representative hardware and record that version in your release matrix.

Qualify the agent release #

Use a capability check rather than relying on a version string alone:

  1. Read the device's reported agent version in the engineering workspace.
  2. Confirm the Device Class exposes I/O.
  3. From the product process, connect to 127.0.0.1:18765.
  4. Post a test sample to a staging stream and require a successful response.
  5. Confirm the sample appears in the engineering preview and a customer-scoped portal account.

This is the minimum agent version and capability check you should include in production qualification.

Stream request #

For a single binding, supported agents accept:

text
POST http://127.0.0.1:18765/v1/streams/{binding_id}
Content-Type: application/json

Use the stable Device Class stream ID as {binding_id}. A sample envelope is:

json
{
  "sequence": 1842,
  "timestamp_ms": 1787385600123,
  "body": { "value": 4.2 },
  "metadata": { "source": "cold-room-service" }
}

Field contract #

FieldDevice responsibilityService behaviour
sequenceSend a non-negative counter useful for diagnostics. Persist it across restart if continuity matters to your product.Accepted as an integer. It is not an exactly-once or ordering guarantee.
timestamp_msSend UTC Unix epoch milliseconds from a validated clock.Used as sample time. If omitted, receipt time may be used.
bodySend an object whose value matches the class stream type and unit.Delivered as the sample payload. Do product validation on the device/application side.
metadataSend only non-secret diagnostic context.Optional object; do not treat arbitrary keys as an archival query model.

Current class stream types are Metric, Boolean, Text, JSON, Timeseries, Video, Image, and Events.

Binding IDs are currently limited to 128 characters. Keep them to letters, digits, dots, underscores, colons, and hyphens so the same identifier can be used safely across data and control paths.

An unknown stream binding is not a safe way to create schema dynamically. Define the binding on the Device Class first so type, unit, UI, and customer behaviour are explicit.

Response and retry #

If you implement your own publisher instead of the released helper:

  • treat 202 Accepted as acceptance by the local broker, not proof that a customer rendered the sample;
  • correct 400 payload or path failures before retrying;
  • treat 403 as a local-security failure because the broker accepts loopback clients only;
  • retry 503 broker_not_ready and transient 500 enqueue_failed with capped exponential backoff and jitter;
  • keep the sampling loop independent from the upload retry loop;
  • avoid an unbounded in-process queue.

The current local broker accepts JSON object requests up to 256 KiB including metadata. On success it returns 202 with the binding and sequence queued. It returns 400 for malformed or unsupported requests, 403 for non-loopback clients, 503 while the disk buffer is unavailable, and 500 if enqueueing fails unexpectedly.

The cloud ingest service can return 201 with accepted sample metadata and a limits object, 400 for an invalid envelope, 403 for the wrong device identity, and 429 with Retry-After when a rate budget is exceeded.

No public idempotency key is defined for ordinary stream samples. Samples may be delayed across an offline period. Do not infer ordering from arrival order; use timestamp_ms, sequence, and your own domain identifier where ordering matters.

Rates, batches, and retention #

Current service defaults include:

  • up to 2,000 samples in one cloud batch;
  • up to 360 cloud batches per hour per device by default;
  • plan- and stream-dependent per-channel budgets.

Use the limits policy returned to the agent rather than hard-coding those defaults into your product.

Class history settingMeaningDesign consequence
Live onlyLast-known value for current UIIntermediate samples may not remain queryable.
Keep historyRecorded samples subject to plan retention and rate limitsHistory is finite and is not an archival system of record.

Offline buffer and restart behaviour #

The agent handles cloud authentication, buffering, retry, and reconnect.

The current agent stores accepted samples in a SQLite offline buffer under its persistent state directory. Its default cap is 20,000 queued rows and its current upload batch cap is 2,000 rows. When the queue exceeds the configured row limit, oldest volatile rows are discarded before durable rows.

The queue normally survives an agent process restart and device reboot while the persistent state directory remains intact. It does not survive deletion, corruption, reimaging, or loss of that storage.

Those queue values are implementation defaults, not a substitute for application-owned durability. If loss would be commercially or physically unacceptable, keep a durable journal in your own product and reconcile it using a stable domain event ID.

A stale widget is a presentation state, not a device watchdog. Keep safety interlocks, local alarms, and fail-safe control in the product application.

Actions: requests from UI to device #

An action definition configures a permitted request in I/O. Placing a button only configures the UI and server-side allowlist; the UI does not implement the physical behaviour.

The device application should:

  1. receive the agent-delivered oem.action message;
  2. validate the binding and method;
  3. authorise the operation against local state;
  4. apply timeouts, interlocks, and safety rules;
  5. execute the operation;
  6. publish result or state separately when confirmation matters.

Current server methods are get, on, off, set, and do. An accepted request can report queued; that is not proof that the device executed the action.

The generic Device Class action path is deliberately lightweight. It does not itself provide a durable domain contract for idempotency, expiry, cancellation, or acknowledgement, and accepted actions use the in-memory inbox described below.

Do not generalise that limitation to Product Applications. When a workflow needs durable control semantics, expose the action through a Product Application command. Product Application commands use durable invocation identity and track delivery, acknowledgement, execution, terminal result, deadline/expiry, cancellation, and controlled redelivery. They also accept an idempotency key so the same cloud-side command request can resolve to the existing invocation rather than create another. Product Application operations likewise support idempotent invocation when a key is supplied.

Those guarantees still stop at the request boundary. A physical or commercial side effect is not magically exactly-once: the device or authoritative backend must recognise a repeated invocation where necessary, apply interlocks, and reconcile the resulting state.

In the current supporting agent, generic Device Class actions enter an in-memory inbox of at most 256 requests. A container reads them with GET /v1/actions or GET /v1/actions/{binding_id}. The oldest request is discarded if the inbox is full, and pending generic actions do not survive an agent process restart or device reboot. This inbox is separate from the durable Product Application command lifecycle.

Settings: desired and reported state #

A setting is lasting desired state, not an action result. Current setting types are boolean, bounded number, and enum.

When an operator changes a setting, the backend stores desired state and emits oem.setting to an online device. The update manifest also carries the full desired snapshot so a reconnecting or restarted device can converge.

In the supporting field agent (0.1.52+), desired settings are persisted under:

text
/var/lib/dataplicity/config/current/parameters.json

They are exposed to class containers without device credentials through:

  • GET /v1/settings or GET /v1/settings/{key};
  • optional long-poll with ?wait=<seconds>&since=<generation>;
  • POST /v1/settings/{key}/reported for reported state;
  • a read-only bind mount of config/current at /etc/dataplicity/config.

The service keeps desired and reported values and timestamps separately. Device software still decides whether the value is valid, applies it, and reports the resulting state.

Design setting handlers to tolerate duplicate delivery and to be safe across reconnects and restarts.

Production acceptance #

Test streams, actions, settings, and Product Application commands with:

  • broker absent, starting, and restarting;
  • device offline and reconnecting;
  • application restart and full power loss;
  • duplicate and out-of-order domain events;
  • invalid type, out-of-range number, unknown stream binding, and clock skew;
  • rate limiting and a full local queue;
  • customer viewer, operator, and administrator roles;
  • a generic action repeated after an ambiguous response;
  • a Product Application command redelivered before or after acknowledgement;
  • expiry and cancellation of a Product Application command;
  • desired state the device rejects or cannot apply.