> For the complete documentation index, see [llms.txt](https://docs.cybus.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cybus.io/2-4-2/guides/system-connectivity/industry-standards-interoperability/sparkplug-b-integration.md).

# Sparkplug B Integration

This guide describes how to use Connectware as the central MQTT broker (MQTT Server) of a Sparkplug B infrastructure. Sparkplug edge nodes and host applications, such as a SCADA system, connect to the Connectware broker, and Connectware provides the users, permissions, and routing for the Sparkplug namespace. A complete example file is available at the end of this guide.

## Objectives

* Understanding the Sparkplug B topic namespace and how Connectware handles Sparkplug traffic.
* Creating Connectware users and roles with least-privilege permissions for edge nodes and host applications.
* Understanding the STATE mechanism that announces the availability of a host application.
* Mirroring Sparkplug traffic to internal topics for monitoring.

## Prerequisites

To follow this guide, you will need the following:

* A running instance of Cybus Connectware.
* One or more Sparkplug B edge nodes, for example devices based on the [Eclipse Tahu](https://projects.eclipse.org/projects/iot.tahu) client libraries or an Ignition gateway with the MQTT Transmission module, and a Sparkplug host application, such as a SCADA system. All of them must have network access to the Connectware MQTT broker on port `8883` (TLS) or `1883`.
* Access to the [Admin UI](/2-4-2/access/admin-ui.md) with sufficient [user permissions](/2-4-2/access/user-management.md).
* Basic knowledge of MQTT and the Connectware [services](/2-4-2/data-flows/services.md) concept (for example, [service commissioning files](/2-4-2/data-flows/service-commissioning-files.md), [mappings](/2-4-2/data-flows/service-commissioning-files/resources/cybus-mapping.md), and [roles](/2-4-2/data-flows/service-commissioning-files/resources/cybus-role.md)).
* It helps to be familiar with [Connecting an MQTT Client to Publish and Subscribe Data](/2-4-2/guides/machine-connectivity/gateways-generic-protocols/connecting-an-mqtt-client-to-publish-and-subscribe-data.md).

## Connectware and Sparkplug B Integration

[Sparkplug](https://sparkplug.eclipse.org/) is an open specification by the Eclipse Foundation that defines a topic namespace, a payload encoding, and session state management on top of MQTT. Sparkplug is broker-centric by design: edge nodes publish their data to a central MQTT Server, and host applications consume it from there. The participants never talk to each other directly.

Sparkplug defines the clients, but not the broker. Connectware fills this role:

* **Central broker**: Edge nodes from different vendors and any number of host applications all connect to the same Connectware instance.
* **Governed access**: Connectware [permissions](/2-4-2/access/user-management/permissions.md) restrict each edge node to its own topic subtree, so one misconfigured or compromised node cannot publish data or death certificates for another.
* **Standard MQTT features**: Sparkplug relies on MQTT last will messages and retained messages for its session management. The Connectware broker supports both, so no Sparkplug-specific broker extensions are required.

This guide follows the [Sparkplug 3.0 specification](https://sparkplug.eclipse.org/specification/version/3.0/documents/sparkplug-specification-3.0.0.pdf).

### The Sparkplug B Topic Namespace

All Sparkplug B messages use the following topic structure, where the `device_id` element is only present on device-level messages:

{% code lineNumbers="true" %}

```
spBv1.0/<group_id>/<message_type>/<edge_node_id>[/<device_id>]
```

{% endcode %}

The group ID logically groups edge nodes, for example by site or production line. The combination of group ID and edge node ID must be unique in the entire infrastructure. The message type determines the direction and the semantics of the message:

| Message type | Published by        | Content                                                                        | QoS | Retained |
| ------------ | ------------------- | ------------------------------------------------------------------------------ | --- | -------- |
| `NBIRTH`     | Edge node           | Birth certificate with every metric the edge node will ever report.            | 0   | No       |
| `NDEATH`     | Broker or edge node | Death certificate containing the `bdSeq` number that links it to the `NBIRTH`. | 1   | No       |
| `DBIRTH`     | Edge node           | Birth certificate for a device attached to the edge node.                      | 0   | No       |
| `DDEATH`     | Edge node           | Death certificate for a device that became unavailable.                        | 0   | No       |
| `NDATA`      | Edge node           | Metrics of the edge node that changed since the last message.                  | 0   | No       |
| `DDATA`      | Edge node           | Metrics of a device that changed since the last message.                       | 0   | No       |
| `NCMD`       | Host application    | Command that writes metrics on the edge node, including rebirth requests.      | 0   | No       |
| `DCMD`       | Host application    | Command that writes metrics on a device.                                       | 0   | No       |
| `STATE`      | Host application    | Birth and death certificate of a host application, as JSON.                    | 1   | Yes      |

The QoS and retained flags in the table are mandated by the specification. The clients set them on their own publishes; there is nothing to configure in Connectware for this.

The `NDEATH` message is special: the edge node registers it as an MQTT last will when it connects. If the connection drops unexpectedly, the Connectware broker publishes the `NDEATH` on behalf of the edge node, and every subscribed host application marks the metrics of that node as stale. Before a graceful disconnect, the edge node publishes the `NDEATH` itself. Because the broker publishes the last will under the identity of the edge node client, the edge node user needs write permission on its `NDEATH` topic like on any other topic it publishes to.

The `STATE` message does not follow the standard topic structure. It lives directly under the namespace element:

{% code lineNumbers="true" %}

```
spBv1.0/STATE/<sparkplug_host_id>
```

{% endcode %}

### How Connectware Handles Sparkplug B Payloads

Except for the `STATE` message, all Sparkplug B payloads are binary data, encoded with Google Protocol Buffers (protobuf). Connectware routes these messages like any other MQTT message and enforces permissions on their topics, but it cannot decode the payloads out of the box:

* The [Rule Engine](/2-4-2/data-flows/rule-engine.md) cannot parse, filter, or transform protobuf payloads.
* [Mappings](/2-4-2/data-flows/service-commissioning-files/resources/cybus-mapping.md) can republish the messages to other topics, but the payload passes through unchanged. Do not attach `transform` rules to Sparkplug topics.
* The [Data Explorer](/2-4-2/monitoring/data-explorer.md) shows that messages arrive on a topic, but cannot display the metric values inside.

Consumers that need the decoded metric values must either be Sparkplug-aware themselves, for example a SCADA system or a client built on the Eclipse Tahu libraries, or use a decoding service between the Sparkplug namespace and the rest of your infrastructure, for example a [custom connector](/2-4-2/connectors/custom-connectors.md) that subscribes to the Sparkplug topics and republishes the metrics as JSON.

The one exception is the `STATE` message: its payload is plain JSON, so Connectware can read and transform it. The monitoring mapping in this guide uses this.

{% hint style="info" %}
If your edge nodes are Ignition gateways, you can avoid the protobuf limitation entirely: since release 4.0.25, the MQTT Transmission module includes the UNS Transmitter, which publishes tag values as plain JSON instead of Sparkplug B. For the trade-offs and the setup, see [Ignition Integration](/2-4-2/guides/system-connectivity/mes-erp-business-systems/ignition-integration.md).
{% endhint %}

## Creating Users and Permissions

Edge nodes and host applications authenticate against Connectware with username and password, like any other MQTT client. For more information, see [MQTT Client Authentication](/2-4-2/access/user-management/mqtt-user-authentication.md). We create the users and their roles in the service commissioning file with the [Cybus::User](/2-4-2/data-flows/service-commissioning-files/resources/cybus-user.md) and [Cybus::Role](/2-4-2/data-flows/service-commissioning-files/resources/cybus-role.md) resources.

Do not worry about copying the service commissioning file snippets together into one, the complete example file is available at the end of this guide.

### Integration Parameters

We add the Sparkplug identifiers and the passwords as parameters, so you can set them when you install the service:

* `groupId` and `edgeNodeId`: Identify the edge node in the Sparkplug topic namespace. They must match the values configured on the edge node itself.
* `hostId`: The Sparkplug host ID of the host application, used in the `STATE` topic.
* `edgeNodePassword` and `hostAppPassword`: The passwords for the two MQTT users.

{% code lineNumbers="true" %}

```yaml
parameters:
  groupId:
    description: Sparkplug group ID that the edge node belongs to
    type: string
    default: plant-hamburg

  edgeNodeId:
    description: Sparkplug edge node ID
    type: string
    default: press-line-01

  hostId:
    description: Sparkplug host ID of the host application
    type: string
    default: scada-primary

  edgeNodePassword:
    description: Password for the MQTT user of the edge node
    type: string

  hostAppPassword:
    description: Password for the MQTT user of the host application
    type: string
```

{% endcode %}

### Edge Node User and Role

The edge node publishes its birth, death, and data messages for itself and its attached devices, and subscribes to its own command topics. If the edge node is configured with a primary host application, it also subscribes to the `STATE` topic of that host to detect whether the host is online. The role grants exactly these permissions, restricted to the subtree of this one edge node:

{% code lineNumbers="true" %}

```yaml
resources:
  edgeNodeRole:
    type: Cybus::Role
    properties:
      permissions:
        - resource: !sub 'spBv1.0/${groupId}/NBIRTH/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NDEATH/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NDATA/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DBIRTH/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DDEATH/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DDATA/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NCMD/${edgeNodeId}'
          operation: read
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DCMD/${edgeNodeId}/#'
          operation: read
          context: mqtt
        - resource: !sub 'spBv1.0/STATE/${hostId}'
          operation: read
          context: mqtt

  edge_press_line_01:
    type: Cybus::User
    properties:
      password: !ref edgeNodePassword
      roles:
        - !ref edgeNodeRole
```

{% endcode %}

The resource identifier of the user becomes the username, prefixed with the [service ID](/2-4-2/data-flows/services/serviceid.md) that you assign when you install the service. For example, with the service ID `sparkplug`, the edge node authenticates as `sparkplug.edge_press_line_01`. If you change the `edgeNodeId` parameter, rename the user resource accordingly. When you operate several edge nodes, add one user and one role per node, so each node stays restricted to its own subtree.

{% hint style="info" %}
Instead of defining edge node users in the service commissioning file, you can also let devices register themselves via the [Client Registry](/2-4-2/access/client-registry.md) and assign the permissions in the Admin UI. This is convenient when edge nodes are commissioned one by one on the shop floor.
{% endhint %}

### Host Application User and Role

The host application needs the opposite permissions, across all edge nodes: it subscribes to the entire Sparkplug namespace, publishes commands, and publishes its own `STATE` birth and death certificates. The read permission on `spBv1.0/#` also covers the subscription to its own `STATE` topic, which the specification requires:

{% code lineNumbers="true" %}

```yaml
hostAppRole:
  type: Cybus::Role
  properties:
    permissions:
      - resource: spBv1.0/#
        operation: read
        context: mqtt
      - resource: !sub 'spBv1.0/${groupId}/NCMD/#'
        operation: write
        context: mqtt
      - resource: !sub 'spBv1.0/${groupId}/DCMD/#'
        operation: write
        context: mqtt
      - resource: !sub 'spBv1.0/STATE/${hostId}'
        operation: write
        context: mqtt

scada_primary:
  type: Cybus::User
  properties:
    password: !ref hostAppPassword
    roles:
      - !ref hostAppRole
```

{% endcode %}

To allow the host application to send commands to edge nodes in more than one group, add one `NCMD` and one `DCMD` permission per group.

## The STATE Mechanism

Edge nodes need to know whether their primary host application is online: if it is not, an edge node can buffer data locally instead of publishing into the void. Sparkplug solves this with the `STATE` message on the topic `spBv1.0/STATE/<sparkplug_host_id>`.

The mechanism uses the same last will pattern as the `NDEATH` message, but with a plain JSON payload:

1. When the host application connects, it registers an MQTT last will on its `STATE` topic with the payload `{"online": false, "timestamp": ...}`, QoS 1, and the retained flag.
2. Immediately after subscribing to its own `STATE` topic, the host application publishes `{"online": true, "timestamp": ...}` as a retained message with QoS 1. The timestamp matches the one in the registered last will.
3. If the host application disconnects gracefully, it publishes the `online: false` payload itself. If the connection drops unexpectedly, the Connectware broker publishes the registered last will instead.

Because the message is retained, the `STATE` topic always carries the current availability of the host application. An edge node that connects later receives it immediately, without waiting for the next state change.

{% hint style="info" %}
Clients that implement Sparkplug 2.2 or earlier publish the `STATE` message on the legacy topic `STATE/<host_id>`, outside the `spBv1.0` namespace, with the plain-text payloads `ONLINE` and `OFFLINE`. If your infrastructure mixes specification versions, grant the host application write permission on `STATE/${hostId}` and the edge nodes read permission on it, in addition to the permissions shown in this guide.
{% endhint %}

## Mirroring Sparkplug Traffic for Monitoring

Consumers outside the Sparkplug infrastructure sometimes need visibility into the namespace without receiving Sparkplug permissions themselves, for example a diagnostics dashboard that shows which nodes are alive. A [Cybus::Mapping](/2-4-2/data-flows/service-commissioning-files/resources/cybus-mapping.md) can mirror the Sparkplug namespace to an internal topic subtree. The named wildcard `#topic` carries the remainder of the topic path over to the output topic:

{% code lineNumbers="true" %}

```yaml
sparkplugMonitorMapping:
  type: Cybus::Mapping
  properties:
    mappings:
      # Mirrors the entire Sparkplug namespace. The protobuf payloads
      # pass through unchanged.
      - subscribe:
          topic: 'spBv1.0/#topic'
        publish:
          topic: 'sparkplug/monitor/#topic'

      # The STATE payload is JSON, so a transform rule can process it.
      - subscribe:
          topic: 'spBv1.0/STATE/+host'
        publish:
          topic: 'sparkplug/host-state/$host'
          retain: true
        rules:
          - transform:
              expression: >
                {
                  "online": online,
                  "timestamp": timestamp
                }
```

{% endcode %}

The first mapping entry re-routes every Sparkplug message to the `sparkplug/monitor` subtree. The payloads remain protobuf-encoded binary; the mapping changes where the messages go, not what they contain. Consumers of the mirrored topics can observe topic activity and message frequency, but still need a Sparkplug decoder to read the metric values.

The second mapping entry handles the one JSON message in the namespace: it republishes the availability of every host application to a retained topic per host, where any dashboard or alerting service can consume it directly.

## Bridging Sparkplug Traffic to Another Broker

To forward Sparkplug traffic to an MQTT broker outside Connectware, use the [MQTT connector](/2-4-2/connectors/enterprise-connectors/mqtt.md). It passes payloads through unmodified in both directions, so the protobuf encoding is preserved.

A forwarded copy of the traffic is informational only: Sparkplug session management depends on last will messages, retained `STATE` messages, and `bdSeq` numbers on the broker that the clients are connected to. A bridge does not replicate this session state. Sparkplug participants that take part in the birth and death handshake must connect directly to the Connectware broker.

## Verifying the Integration

1. Install the service and set the parameters, including the two passwords. See [Installing Services](/2-4-2/data-flows/services/managing/installing.md).
2. Configure the edge node and the host application with the Connectware hostname, port `8883` (TLS) or `1883`, and their usernames and passwords. Remember that the usernames are prefixed with the service ID, for example `sparkplug.edge_press_line_01` and `sparkplug.scada_primary`.
3. Open the [Data Explorer](/2-4-2/monitoring/data-explorer.md) and subscribe to `spBv1.0/#`. When the host application connects, its retained `STATE` message appears. When the edge node connects, its `NBIRTH` and `DBIRTH` messages follow. The payloads of the birth and data messages are binary and are not displayed as readable values; seeing the messages arrive on the correct topics is the verification.
4. Verify the permission enforcement: publish to the subtree of a different edge node with the edge node credentials, for example to `spBv1.0/plant-hamburg/NDATA/some-other-node`. The Connectware broker denies the publish, and the message does not appear in the Data Explorer.

To check the mirrored host state, subscribe with an MQTT client such as `mosquitto_sub`:

{% code lineNumbers="true" %}

```bash
mosquitto_sub -h ${CONNECTWARE_HOST} -p 1883 \
  -u ${MONITOR_USER} -P ${MONITOR_PASSWORD} \
  -t 'sparkplug/host-state/#' -v
```

{% endcode %}

Replace the following values:

* `${CONNECTWARE_HOST}`: The hostname or IP address of your Connectware instance.
* `${MONITOR_USER}` and `${MONITOR_PASSWORD}`: A Connectware user with read permission on `sparkplug/#`, for example your own user.

The output shows one retained message per host application, for example `sparkplug/host-state/scada-primary {"online":true,"timestamp":1752664800000}`.

## Service Commissioning File Example

{% file src="/files/QOUzWj0DkgdsJrSgEZRE" %}

{% code title="sparkplug-b-example.yml" lineNumbers="true" expandable="true" %}

```yaml
---
# ----------------------------------------------------------------------------
# Service Commissioning File
# ----------------------------------------------------------------------------
# Copyright: Cybus GmbH
# Contact: support@cybus.io
# ----------------------------------------------------------------------------
# Sparkplug B Integration (Example)
# ----------------------------------------------------------------------------

description: >
  Service commissioning file for using Connectware as the central MQTT
  broker of a Sparkplug B infrastructure (Example)

metadata:
  name: Sparkplug B Integration
  provider: cybus
  homepage: https://www.cybus.io
  version: 1.0.0

parameters:
  groupId:
    description: Sparkplug group ID that the edge node belongs to
    type: string
    default: plant-hamburg

  edgeNodeId:
    description: Sparkplug edge node ID
    type: string
    default: press-line-01

  hostId:
    description: Sparkplug host ID of the host application
    type: string
    default: scada-primary

  edgeNodePassword:
    description: Password for the MQTT user of the edge node
    type: string

  hostAppPassword:
    description: Password for the MQTT user of the host application
    type: string

resources:
  # Permissions for one edge node: publish its own birth, death, and
  # data messages, subscribe to its command topics and the host STATE
  edgeNodeRole:
    type: Cybus::Role
    properties:
      permissions:
        - resource: !sub 'spBv1.0/${groupId}/NBIRTH/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NDEATH/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NDATA/${edgeNodeId}'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DBIRTH/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DDEATH/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DDATA/${edgeNodeId}/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NCMD/${edgeNodeId}'
          operation: read
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DCMD/${edgeNodeId}/#'
          operation: read
          context: mqtt
        - resource: !sub 'spBv1.0/STATE/${hostId}'
          operation: read
          context: mqtt

  # MQTT user for the edge node. The username is prefixed with the
  # service ID, for example sparkplug.edge_press_line_01
  edge_press_line_01:
    type: Cybus::User
    properties:
      password: !ref edgeNodePassword
      roles:
        - !ref edgeNodeRole

  # Permissions for the host application: subscribe to the entire
  # Sparkplug namespace, publish commands and its own STATE message
  hostAppRole:
    type: Cybus::Role
    properties:
      permissions:
        - resource: spBv1.0/#
          operation: read
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/NCMD/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/${groupId}/DCMD/#'
          operation: write
          context: mqtt
        - resource: !sub 'spBv1.0/STATE/${hostId}'
          operation: write
          context: mqtt

  # MQTT user for the host application
  scada_primary:
    type: Cybus::User
    properties:
      password: !ref hostAppPassword
      roles:
        - !ref hostAppRole

  # Mirrors the Sparkplug namespace to internal topics for monitoring.
  # The protobuf payloads pass through unchanged. The STATE payload is
  # JSON, so a transform rule can process it.
  sparkplugMonitorMapping:
    type: Cybus::Mapping
    properties:
      mappings:
        - subscribe:
            topic: 'spBv1.0/#topic'
          publish:
            topic: 'sparkplug/monitor/#topic'
        - subscribe:
            topic: 'spBv1.0/STATE/+host'
          publish:
            topic: 'sparkplug/host-state/$host'
            retain: true
          rules:
            - transform:
                expression: >
                  {
                    "online": online,
                    "timestamp": timestamp
                  }
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.cybus.io/2-4-2/guides/system-connectivity/industry-standards-interoperability/sparkplug-b-integration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
