SparkplugB is an optional Lua plugin for publishing node and device metrics over MQTT and receiving commands. It acts as a Sparkplug Edge of Network (EoN) node. This reference explains how to create a client, publish values and handle commands in Barracuda App Server (BAS), Mako Server and Xedge applications.
The plugin implements a subset of Sparkplug 3.0. Read Current limitations before integrating it. The implementation and protobuf schema are maintained in BAS-Resources.
For runnable node and console-monitor applications, see the Sparkplug examples. Their README covers configuration, starting the applications and packaging for Xedge.
The runtime must include the native pb module and the Lua modules protoc, SparkplugB, EventEmitter and MQTT clients. The resource file .lua/sparkplug_b.proto must be available through ba.openio"vm". Availability depends on how Mako or Xedge was built and packaged. Loading SparkplugB throws if its protobuf dependencies or schema are unavailable. An MQTT broker must be accessible from the runtime.
Node topics have the form spBv1.0/groupId/messageType/nodeName; device topics append /deviceId.
| Message | Purpose |
|---|---|
| NBIRTH / DBIRTH | Publish the node's or device's metric definitions and current values. |
| NDATA / DDATA | Publish changed node or device values. |
| NCMD / DCMD | Receive node or device commands. |
| NDEATH | MQTT will message registered by the plugin for the node. |
| DDEATH | Application notification that a device is offline. |
The plugin starts with MQTT 5 and can fall back to MQTT 3.1.1. It reconnects automatically while running. The application supplies birth messages in its birth event handler; the plugin does not retain and retransmit the application's birth payload by itself.
This example uses a local broker without authentication. Set connection options for your broker. Install the code as an application's .preload so onunload() owns cleanup.
local SP = require"SparkplugB"
local deviceId = "device-1"
local nodeMetrics = {SP.metric("temperature", "double", 21.5)}
local deviceMetrics = {SP.metric("enabled", "boolean", false)}
local client = SP.create("localhost", "example-group", "example-node")
client:on("birth", function()
-- Supply fresh definitions and values after each successful subscription setup.
client:publishNodeBirth{metrics=nodeMetrics}
client:publishDeviceBirth(deviceId, {metrics=deviceMetrics})
end)
client:on("connect", function()
trace("Sparkplug subscriptions ready")
end)
client:on("error", function(message, status)
trace("Sparkplug error", message, status)
end)
client:on("dcmd", function(id, payload)
-- Apply only this example's known Boolean device command.
if id ~= deviceId then return end
for _, metric in ipairs(payload.metrics) do
if metric.name == "enabled" and metric.type == "boolean"
and type(metric.value) == "boolean" then
deviceMetrics[1].value = metric.value
client:publishDeviceData(id, {metrics=deviceMetrics})
end
end
end)
function onunload()
-- Stop owned network activity when the application is unloaded.
client:stop()
end
Load the module with local SP = require"SparkplugB". In the signatures below, square brackets mark optional arguments. table objects belong to this Lua implementation; they are not native userdata handles.
Incorrect API usage can throw. Network and broker outcomes are reported through events because connection and publication work runs asynchronously. Event callbacks' return values are ignored. The underlying EventEmitter catches callback exceptions and traces them; callbacks should still handle their own application failures.
Parameters
Return values
birth, connect and error.Throws
Throws for incorrect argument types or invalid connection options. Connection and broker failures are delivered through events, not a synchronous nil, error result.
true uses the runtime's SharkSSL client; a SharkSSL client object supplies your own TLS configuration. Omitted/false uses plain TCP.See the MQTT 5 and MQTT 3.1.1 references for transport details. The plugin manages its own subscriptions and will; it does not expose all underlying MQTT methods.
Parameters
None.
Return values
true on the first call, false if already stopped. The first call closes an existing socket and emits close once. It is valid to stop before a socket has been created.Throws
Does not deliberately throw for an ordinary stop. Calling the method on an invalid object is incorrect API usage. Pending subscription acknowledgements cannot emit birth or connect after stopping.
Stopping is terminal for this client. Create a new client to start again. All five publish methods throw "client stopped" after stopping.
Parameters
None.
Return values
stop(), including its one-time close event.Throws
The same behavior as stop(): no deliberate operational exception; an invalid receiver is incorrect usage.
Parameters
Return values
No values. Registering the same function for the same event does not add another copy. An unrecognized event name does not create a new plugin event.
Throws
Incorrect receiver, event or callback usage can fail during registration or invocation. Callback exceptions are caught and traced by EventEmitter. A callback result does not cancel an event or control reconnect behavior.
Parameters
int is an alias of int32.nil creates a metric with is_null=true.Return values
name, type, value, alias, timestamp and is_null fields as applicable. The value is retained directly, including a table value. The helper does not perform full payload validation.Throws
Throws for an unknown type name or an invalid type argument. Other invalid fields or values can fail later during encoding.
Parameters
metrics array is required; it may be empty.Return values
seq to 0 but does not supply a payload timestamp or add node metrics.Throws
Throws for invalid payload structure, missing required Lua fields, unknown type names or values the protobuf encoder cannot encode. The normalized table uses wire field names; use the original payload or SP.decode(bytes) when calling SP.encode() again.
Parameters
Return values
metrics table. Unsupported or missing datatypes return nil, "unsupported datatype". Conversion failures and errors detected by the native protobuf decoder are returned, not thrown.Signed Int8/Int16/Int32 values are converted according to their declared widths. Dataset cells are plain values. Template references use templateRef. Null values and empty collections are retained. See the numeric and metadata limitations below.
Throws
Throws if bytes is not a string. Does not deliberately throw for invalid received payload data.
The conversion helpers use ordinary Lua tables instead of protobuf oneof field names such as int_value and boolean_value.
SP.encode() does not.SP.encode() preserves it or defaults it to 0.value. SP.metric(..., nil) sets this automatically.SP.metric() supplies it; hand-written metric tables do not get an automatic metric timestamp.| Datatype | Lua value |
|---|---|
int8, int16, int/int32, int64 |
Signed integer value within the selected width and the runtime's representable range. |
uint8, uint16, uint32, uint64 |
Nonnegative integer value within the selected width and the runtime's representable range. |
float, double |
Number; precision depends on Lua and protobuf storage. |
boolean |
Boolean, including false. |
string, text, uuid |
String. |
datetime |
Numeric Unix timestamp in milliseconds. |
bytes, file |
Binary string. |
dataset |
Dataset table. |
template |
Template table. |
The plugin performs conversion, not exhaustive validation of all Sparkplug rules. Supply valid datatype/value combinations and protocol fields. propertyset and propertysetlist describe property values, not ordinary metric values. Array datatypes are not implemented.
A dataset value has table columns (array of column-name strings), table types (matching array of datatype-name strings), and table rows (array of rows). Each row is an array of plain scalar values, not {value=...} wrappers. Column/type counts and row widths must match. Use scalar datatypes supported by the schema. Supply integer num_of_columns when the wire payload needs that field; the encoder does not calculate it automatically.
-- Plain cell values are used both before encoding and after decoding.
local metric = SP.metric("samples", "dataset", {
num_of_columns=2,
columns={"temperature", "enabled"},
types={"double", "boolean"},
rows={{21.5, false}, {22.0, true}}
})
local normalized, bytes = SP.encode{metrics={metric}}
local payload, err = SP.decode(bytes)
assert(payload, err)
assert(payload.metrics[1].value.rows[1][2] == false)
Empty row lists and empty collections decode as Lua tables, so they can be re-encoded.
A template value contains:
isDefinition omitted/false); refers to its definition.Each parameter contains string name, string type, and a matching number, boolean or string value supported by the schema. false is a value; a missing value is an encoding error. Decoding restores templateRef and empty metric/parameter lists.
A property set is table properties with table keys (array of strings) and table values (matching array of property tables). Each property has string type and its matching number, boolean, string or table value. For a null property, set boolean is_null to true and omit value.
Use scalar types supported by the schema, or these nested forms:
propertyset: table value containing keys and values arrays.propertysetlist: table value containing table propertyset, an array of property sets.Boolean false, explicitly null properties and empty nested collections survive encoding and decoding. The encoder requires the named array fields even when they are empty.
All five methods copy the supplied payload recursively, assign the payload timestamp/sequence as described above, and queue a QoS 0 MQTT publication. They return no delivery acknowledgement. While the client is running but disconnected, messages can be queued for later sending. Publish normal application data after connect; publish definitions from the birth handler.
Parameters
Return values
No values. Resets sequence to 0 and adds an Int64 bdSeq metric to the copied payload.
Throws
Throws after stopping, or for invalid payloads/values during encoding.
Parameters
Return values
No values. Queues DBIRTH using the shared sequence counter.
Throws
Throws after stopping, or for invalid device/payload arguments or encoding values.
Parameters
Return values
No values. Queues NDATA using the shared sequence counter.
Throws
Throws after stopping, or for invalid payloads/values during encoding.
Parameters
Return values
No values. Queues DDATA using the shared sequence counter.
Throws
Throws after stopping, or for invalid device/payload arguments or encoding values.
Parameters
{} or a table with number timestamp in Unix milliseconds. A table is required.Return values
No values. Queues DDEATH. The copied payload's metrics array is cleared; any metrics supplied by the caller are not sent.
Throws
Throws after stopping, or for invalid device/payload arguments or encoding values.
There is no node-death publishing method. The plugin registers an NDEATH will containing bdSeq when connecting. The MQTT broker publishes that will according to the connection's will rules. Do not assume the close event means that the broker or another client has received it.
Use client:on(event, callback). The following table lists every event emitted by this implementation. There is no generic command event.
| Event | Callback arguments: name and type | When it occurs |
|---|---|---|
birth |
No arguments after subscription setup; table metric for a node-control rebirth request. | After all three subscriptions succeed, before connect; also for a Node Control/Rebirth metric whose type is Boolean and value is true. The application should publish its birth messages. |
connect |
None. | After successful subscription setup and the birth event. A refused subscription suppresses both events, closes the socket and lets reconnection retry. |
reboot |
table metric. | For a Boolean Node Control/Reboot value of true. The plugin emits an event; it does not reboot the device itself. |
ncmd |
table payload. | A successfully decoded NCMD. Recognized node-control metrics are removed; remaining metrics form a dense array in incoming order. The array may be empty. |
dcmd |
string deviceId, table payload. | A successfully decoded DCMD for the device component in the topic. |
state |
string id, string payload. | A matching spBv1.0/STATE/id message. id is the third topic component. The payload is passed through as a string; JSON is not decoded here. |
offline |
None. | Loss of an established connection, or server shutdown reported by MQTT. |
reconnect |
None. | After an established connection is lost while the client is running, before retry. Initial connection retries need not emit it. |
error |
string or integer message, integer or nil status. | MQTT connection/read errors, broker or subscription refusals, unsupported/malformed payloads, or unrecognized incoming topics. The optional status carries a broker/subscription reason code when supplied. |
close |
None. | The first explicit stop()/close() call, including before a connection exists. Repeated calls do not emit it again. |
False, null and non-Boolean node-control values do not emit birth or reboot. They are still removed from ncmd's ordinary metric list. Unsupported datatypes or payload conversion failures emit error instead of dispatching that command.
nil, "unsupported datatype"; received commands emit an error. An alias with a supplied datatype can reach application code, but there is no automatic alias-to-name lookup. Encoding requires metric names. Metadata tracking is deferred.0x80 is currently accepted as an empty message. This native issue is deferred.These limits mean that the plugin is not a complete Sparkplug 3.0 implementation. Check interoperability with the target broker and host application. Protobuf roundtrip tests and controlled MQTT tests do not establish full protocol conformance or hardware behavior.