---
title: Sparkplug Data
slug: solutions/sparkplug-data
docTags: 
createdAt: 2026-08-31T22:48:30.157Z
---

Every message the node publishes or receives sits under the Sparkplug B namespace, built from your Group ID, the message type, and your Node ID:

```javascript
spBv1.0/{group_id}/{message_type}/{node_id}[/{device_id}]
```

For `GROUP_ID=Plant1` and `NODE_ID=Line1`, a device named `press_002` publishes on `spBv1.0/Plant1/DDATA/Line1/press_002`. The device segment is the DeviceHub device name, or the Digital Twin instance's Sparkplug device ID; node-level messages have no device segment.

## Message Types

| Type     | Direction      | Topic                           | QoS            | Retained        | Purpose                                                           |
| -------- | -------------- | ------------------------------- | -------------- | --------------- | ----------------------------------------------------------------- |
| `NBIRTH` | Node to broker | `spBv1.0/{group}/NBIRTH/{node}` | 0              | No              | Opens a session and declares node metrics                         |
| `DBIRTH` | Node to broker | `…/DBIRTH/{node}/{device}`      | 0              | No              | Declares one device's metrics, with names, aliases, and datatypes |
| `DDATA`  | Node to broker | `…/DDATA/{node}/{device}`       | 0              | No              | Device metric values, referenced by alias                         |
| `NDATA`  | Node to broker | `…/NDATA/{node}`                | 0              | No              | Node-level metric values                                          |
| `DDEATH` | Node to broker | `…/DDEATH/{node}/{device}`      | 0              | No              | One device is no longer publishing                                |
| `NDEATH` | Node or broker | `…/NDEATH/{node}`               | 1              | No              | The session ended                                                 |
| `NCMD`   | Host to node   | `…/NCMD/{node}`                 | 1 subscription | —               | Command for the node                                              |
| `DCMD`   | Host to node   | `…/DCMD/{node}/{device}`        | 1 subscription | —               | Command for one device                                            |
| `STATE`  | Host to broker | `spBv1.0/STATE/{host_id}`       | 1 subscription | Set by the host | Host application liveness                                         |

`NDEATH` is registered as the MQTT Will Message when the node connects, at QoS 1 and not retained, so the broker publishes it if the node disappears without warning — that is what makes an unplugged device visible to host applications. On a clean shutdown the node publishes `NDEATH` itself, so the message arrives once either way.

## Birth Order

Host applications reject data for a node they have not seen born, so ordering is strict:

```mermaid
flowchart LR
    NB[NBIRTH] --> DB[DBIRTH per device] --> DD[DDATA]
```

The node holds its publish lock until the broker acknowledges each birth message, so no data message can overtake the birth set. Nothing is published at all while the birth set is being rebuilt.

## Sequence Numbers

Two counters appear in payloads, and host applications check both.

`seq` orders messages within one session. `NBIRTH` carries `0`, each following data or birth message increments by one, and the value wraps to `0` after `255`. `NDEATH` carries no `seq` at all. The node consumes a sequence number only for a message it actually sends — a tag update that produces no publishable metric does not advance the counter, which is what keeps host applications from reporting a gap.

`bdSeq` identifies the session itself. The `NBIRTH` that opens a session and the `NDEATH` that closes it carry the same value, and the next session uses the next value, wrapping after 255. It is stored at `<CONFIG_PATH>/seqNum/<group>-<node>_bdSeq.txt`, which must survive restarts — see [Deployment](#). A rebirth requested over `NCMD` reuses the current `bdSeq`, because the MQTT session has not changed.

## Aliases

Birth messages declare every metric with a name, a numeric alias, and a datatype. Data messages then carry only the alias and the value, omitting names and datatypes entirely; a null value is sent as the alias with the null flag set.

Aliases come from stable identities — the device and tag IDs for DeviceHub, the device and metric key for Digital Twins — so a metric keeps its alias across a rebirth, and a host application can keep resolving in-flight data after a topology change. They are assigned per process, so a restarted node reassigns them and host applications must re-read them from each new `NBIRTH` rather than caching them across sessions.

## DeviceHub Devices and Tags

Each DeviceHub device becomes one Sparkplug device, and each of its tags becomes one metric on that device. The node reads device and tag metadata from the Litmus Edge API to build birth certificates, then publishes values as they arrive.

The Sparkplug device ID is the DeviceHub device name, trimmed of surrounding whitespace; a device with no name falls back to its Litmus Edge device ID. That name is the last segment of the device's topics, so renaming a device in DeviceHub changes its topic and host applications see a new device.

| Metric group in `DBIRTH` | Contents                                                                                                                                      |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `Tags/<tag name>`        | One metric per tag, with the tag's datatype, an alias, and a typed initial value                                                              |
| `Device Properties/…`    | Device metadata: ID, name, description, driver ID, name, description, and version, device type ID and name, creation timestamp, and tag count |
| `Device Control/Rebirth` | Boolean command metric a host application can write to request this device's birth certificate again                                          |

Tag metrics keep the tag name as configured in DeviceHub, so a tag named `Bearing Temp` publishes as `Tags/Bearing Temp`. Tag metadata entries defined in DeviceHub travel with the metric as properties, named `metadata_<key>` with non-alphanumeric characters in the key replaced by `_`.

### Datatypes

Litmus Edge tag types map to Sparkplug datatypes as follows. Matching is case-insensitive.

| Litmus Edge value type                                                                                        | Sparkplug datatype |
| ------------------------------------------------------------------------------------------------------------- | ------------------ |
| `bit`, `bit(int)`, `bin`, `bool`, `int(bit)`                                                                  | `Boolean`          |
| `uint8`, `byte`, `octal`, `left_byte`, `right_byte`                                                           | `UInt8`            |
| `uint16`, `word`                                                                                              | `UInt16`           |
| `uint32`, `dword`, `usint`, `uint`                                                                            | `UInt32`           |
| `uint64`, `lword`, `ulint`, `udint`, `ulong`                                                                  | `UInt64`           |
| `int8`, `sint`, `int16`, `short`, `float`, `real`, `float32`, `flt32`, `fl`                                   | `Float`            |
| `int`, `int32`, `int64`, `lint`, `long`, `dint`, `double`, `float64`, `lreal`, `number`, `decimal`            | `Double`           |
| `string`, `char`, `date`, `time`, `json`, `hex`, `raw`, `blob`, `counter`, `timer`, and other text-like types | `String`           |

A tag whose Count property is greater than 1 publishes as the matching array type instead: `BooleanArray`, `UInt32Array`, `UInt64Array`, `FloatArray`, `DoubleArray`, or `StringArray`.

A tag with a value type the node does not recognize is left out of the birth certificate, so it never appears in the host application. A tag whose name contains no alphanumeric characters, or is not valid UTF-8, is also skipped, with an error logged naming the tag.

### Values and Lifecycle

Tag updates arrive from the Litmus Edge NATS broker and publish as `DDATA` on the device's topic, carrying the alias declared at birth and the value. Each update publishes as it arrives; the node adds no batching delay of its own. The metric timestamp is the event time supplied by Litmus Edge, so history in your host application reflects when the value was read rather than when it was published.

The node also subscribes to Litmus Edge lifecycle events:

| Event                                                                              | Reaction                                                                          |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `device.enabled`, `device.connected`                                               | Publish `DBIRTH` for that device                                                  |
| `device.removed`, `device.disabled`, `device.disconnected`, `device.start.failed`  | Publish `DDEATH` for that device                                                  |
| `device.created`, `device.updated`, `tags.created`, `tags.updated`, `tags.removed` | Refresh metadata and republish the birth set if the advertised metric set changed |

Paired events are de-duplicated, so a device that reports both `disabled` and `disconnected` produces one `DDEATH`. Litmus Edge sometimes emits an event before the API reflects the change, so the node re-checks shortly afterwards, comparing the new topology against the old one and publishing nothing when nothing changed.

Host applications do not reliably retire a metric that simply disappears from a later birth certificate. So when a tag set shrinks, the node publishes `DDEATH` for the affected device first, then a fresh `NBIRTH` and `DBIRTH` with the current tags, and no data for that device in between. The result is a brief gap rather than a stale tag that keeps reporting good quality.

## Digital Twins

Each enabled Digital Twin instance becomes one Sparkplug device, and each of its dynamic attributes becomes one metric. Unlike a DeviceHub tag, a twin attribute has no inherent datatype, so the node decides every metric's datatype from the twin's model before any value flows — and never changes it because of a runtime sample.

```mermaid
flowchart LR
    MODEL[Model, hierarchy, schema, source tags] --> DBIRTH[DBIRTH contract]
    RUNTIME[Runtime payloads] --> DDATA[DDATA values only]
    DBIRTH --> HOST[Host application]
    DDATA --> HOST
```

The birth certificate declares the metric names, aliases, and datatypes; runtime payloads then supply values for metrics that already exist. A field that appears in a payload but not in the contract is ignored, which keeps a host application's tag list stable.

An instance is published only when it is enabled and has a topic. One whose contract compiles to no metrics at all is skipped, and counted by `le_sparkplug_digital_twin_quarantine_total`.

| Metric group in `DBIRTH`           | Contents                                                                |
| ---------------------------------- | ----------------------------------------------------------------------- |
| `Attributes/…`                     | One metric per dynamic attribute, at its path in the model hierarchy    |
| `Device Properties/Digital Twin/…` | Model and instance diagnostics                                          |
| `Device Control/Rebirth`           | Boolean command metric to request this device's birth certificate again |

A twin's Sparkplug device ID is namespaced separately from DeviceHub device names, so a twin and a device can share a display name without colliding.

### How Each Metric Gets Its Datatype

Four sources are tried in order. The first that yields a scalar type wins, and the winner is recorded on the metric as the `typeSource` property.

| Order | Source                                        | `typeSource` | Notes                                                                      |
| ----- | --------------------------------------------- | ------------ | -------------------------------------------------------------------------- |
| 1     | The DeviceHub tag the attribute subscribes to | `devicehub`  | The intended path. The tag's value type decides the Sparkplug datatype     |
| 2     | The attribute's own `dataType` field          | `declared`   | Author intent beats a sample-derived schema. `array` becomes `StringArray` |
| 3     | The instance's exported JSON Schema           | `schema`     | Sample-derived. Numbers and integers both become `Double`                  |
| 4     | Fallback                                      | `untyped`    | The metric is registered as `String`                                       |

`untyped` is the honest "not stated anywhere" result rather than a guess: values serialize as text, nothing is dropped, and no datatype is inferred from a name or a unit.

### Metric Properties

Birth metrics carry flat, scalar properties so an operator can tell where a metric came from without server-side lookups. Reading `declaredDataType=json` together with `typeSource=untyped`, for instance, names exactly which field to fix in the model.

| Property                                                                              | Meaning                                                                        |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `attributeId`, `attributeName`                                                        | The model attribute behind the metric                                          |
| `engUnit`                                                                             | The attribute's unit, when the model declares one                              |
| `typeSource`                                                                          | Which of the four sources above set the datatype                               |
| `declaredDataType`                                                                    | The raw `dataType` token the author wrote                                      |
| `topicExpression`                                                                     | The attribute's subscription expression                                        |
| `runtimeValuePath`                                                                    | The field inside the runtime payload holding the value, when it is not `value` |
| `contentType`                                                                         | `json` for metrics whose runtime payload is an object                          |
| `transformationName`, `transformationType`                                            | The configured transformation, when there is one                               |
| `sourceDeviceId`, `sourceDeviceName`, `sourceTagName`, `sourceValueType`, and related | The DeviceHub tag behind a `devicehub`-typed metric                            |

### Runtime Values

| Situation                                  | Result                                                      |
| ------------------------------------------ | ----------------------------------------------------------- |
| Field present and convertible              | Published as the contract's datatype                        |
| Field absent                               | Skipped, other metrics still publish                        |
| Field explicitly null                      | Published as null for that metric                           |
| Value cannot convert to the birth datatype | That one metric is dropped, the rest of the batch publishes |
| No metric in the batch converts            | Nothing is published, and no sequence number is consumed    |

Compatible shapes are coerced rather than rejected: `"true"`, `0`, and `1` all satisfy a `Boolean`, and `"12.5"` satisfies a `Double`. A value that cannot convert is dropped rather than sent, because a mismatched datatype makes a host application reject the whole payload. Drops are counted by `le_sparkplug_digital_twin_attribute_value_mismatch_total`, labelled with what was expected and what arrived.

Metric timestamps use the value's own timestamp when the payload carries one, then the payload's top-level timestamp, then publish time.

### Changes to a Model

The node polls Litmus Edge for twin changes every 10 seconds, stretching to 60 seconds while nothing changes and returning to 10 seconds as soon as something does. Instance lifecycle events trigger a refresh immediately.

When a contract changes — an attribute added, removed, retyped, or moved — the node publishes `DDEATH` for that device and then a fresh birth certificate, so the host application rebuilds its tag list instead of holding a stale one. A change in values alone publishes data only.

## Commands: NCMD and DCMD

The node subscribes for commands on every connect, at QoS 1: `spBv1.0/{group}/NCMD/{node}` for the node, and `spBv1.0/{group}/DCMD/{node}/#` for any of its devices. It accepts three command metrics, each taking effect when its boolean value is `true`.

| Command metric             | Sent on | Effect                                                                                     |
| -------------------------- | ------- | ------------------------------------------------------------------------------------------ |
| `Node Control/Rebirth`     | `NCMD`  | Refreshes metadata and republishes the full birth set: `NBIRTH` followed by every `DBIRTH` |
| `Node Control/Next Server` | `NCMD`  | Ends the MQTT session and connects to the next broker in the list                          |
| `Device Control/Rebirth`   | `DCMD`  | Republishes that one device's `DBIRTH`                                                     |

Every `NBIRTH` declares the two node commands, and every `DBIRTH` declares the device command, so a host application discovers them from the birth set. A command metric may be addressed by name or by the alias declared at birth.

**Rebirth** is how a host application recovers when it has lost track of the node's metrics, after its own restart or an alias it cannot resolve. The node refreshes DeviceHub and Digital Twin metadata first, then publishes the birth set as one unit, with data publishing closed for that window so nothing arrives referencing aliases the host has not read yet. The session's `bdSeq` does not change, because the MQTT session has not changed. Overlapping requests are suppressed rather than queued: while a birth set is publishing, further requests are counted by `le_sparkplug_rebirth_suppressed_total{reason="active"}` and dropped, so a host application requesting rebirths in a tight loop cannot stall the node.

**Next Server** moves the node to the next broker in `MQTT_SERVERS`: it publishes `NDEATH`, disconnects, and connects to the next entry, starting a new session with the next `bdSeq`. With a single broker configured, it reconnects to that same broker.

Any other command metric is ignored and counted by `le_sparkplug_ncmd_total{type="unknown"}`, with the metric name and value logged at debug level. A malformed metric — no name and no alias — is logged as an error and skipped. Because commands are counted as they arrive, those counters show whether the node received a command a host application believes it sent.

Signed 8-bit and 16-bit integers publish as \`Float\`, and \`int32\`, \`int64\`, and \`dint\` publish as \`Double\`. If your host application expects integer datatypes for those tags, expect floating-point metrics instead.A wave of \`untyped\` metrics on a new instance means its attributes carry no type signal. Fix it in the model editor: bind the attribute to a DeviceHub tag, or set its \`dataType\` to a real scalar. \`le\_sparkplug\_digital\_twin\_attribute\_fallback\_total\` counts these per attribute.Writing values back to devices is not supported. \`DCMD\` handles \`Device Control/Rebirth\` only — there is no path from a Sparkplug command to a DeviceHub tag write.
