Failed to handle Sparkplug B message

Hi all,
I’m currently trying to run the Java reference host application from here in order to read/write tags to a local Ignition instance. I’m running MQTT Engine and Transmission 4.0.18 (b2023081122).

The host application is successfully reading the tag changes but when I try to send a CMD to write a value based on the example they have on the README, MQTT Engine seems to be failing to parse it.

This is the message sent to MQTT according to the Host Application:

Message [topic=spBv1.0/JoseLocal/NCMD/E0, payload=SparkplugBPayload [timestamp=1698877483000, metrics=[Metric [name=Writeable/WriteableBoolean2, alias=null, timestamp=1698877483000, dataType=Boolean, isHistorical=null, isTransient=null, metaData=null, properties=null, value=true, isNull=false]], seq=null, uuid=null, body=null]]

But when MQTT Engine tries to parse it it’s failing with:

Warning [CirrusSparkplugUtil] - Received invalid payload - trying to decode with legacy decoder

Error [SparkplugBPayloadDecoder] - Failed to decode the payload on metric datatype: name: "Writeable/WriteableBoolean2" timestamp: 1698877483000 is_null: false boolean_value: true 

Error [EngineCallback] - java.lang.Exception: Failed to decode: Unknown MetricDataType 0
at com.cirruslink.common.sparkplug.util.LegacyArraySparkplugBDecoder.getMetricValue(LegacyArraySparkplugBDecoder.java:358)
at com.cirruslink.common.sparkplug.util.LegacyArraySparkplugBDecoder.convertMetric(LegacyArraySparkplugBDecoder.java:103)
at com.cirruslink.common.sparkplug.util.LegacyArraySparkplugBDecoder.buildFromByteArray(LegacyArraySparkplugBDecoder.java:73)
at com.cirruslink.common.sparkplug.util.LegacyArraySparkplugBDecoder.buildFromByteArray(LegacyArraySparkplugBDecoder.java:49)
at com.cirruslink.common.sparkplug.util.CirrusSparkplugUtil.decodePayload(CirrusSparkplugUtil.java:71)
at com.cirruslink.common.sparkplug.util.CirrusSparkplugUtil.decodePayload(CirrusSparkplugUtil.java:40)
at com.cirruslink.common.sparkplug.util.CirrusSparkplugUtil.decodePayload(CirrusSparkplugUtil.java:92)
at com.cirruslink.mqtt.engine.gateway.sparkplug.SparkplugBPayloadHandler.decodePayload(SparkplugBPayloadHandler.java:202)
at com.cirruslink.mqtt.engine.gateway.sparkplug.SparkplugBPayloadHandler.decodePayload(SparkplugBPayloadHandler.java:134)
at com.cirruslink.mqtt.engine.gateway.sparkplug.SparkplugPayloadHandler.handlePayload(SparkplugPayloadHandler.java:150)
at com.cirruslink.mqtt.engine.gateway.EngineCallback.lambda$messageArrived$3(EngineCallback.java:338)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

Not sure if I’m doing something wrong in the command payload itself or I’m missing some configuration somewhere.
Any help would be appreciated!

This is due to a bug in MQTT Engine v4.0.18. It doesn’t properly handle CMD messages missing metric data types in the payload. However, this can be safely ignored as MQTT Engine doesn’t act on CMD messages anyway. In v4.0.19 you will no longer see this error message though.

Thanks Wes.

Just for my understanding (I’m just getting started with Ignition), if MQTT Engine doesn’t act on CMD messages, how would a Host Application update tag values via MQTT? Who’s responsible for acting on those? Transmission?

Yes - Host applications like MQTT Engine or the Tahu Host Application send CMD messages. Transmission (or other Edge Node implementations) receive that CMD, write to the output, get the new updated value, send it back to the Host Application(s) via a DATA message. At this point, Engine would update its value of that corresponding tag. You might want to take a look at this doc and leave custom implementations out of the mix to start: Getting Started: Two Ignition Architecture - MQTT Modules for Ignition 8.x - Confluence

1 Like

So if I uninstall the MQTT Engine and try to send the same NCMD payload I still get the decoding error from SparkplugBPayloadDecoder: Failed to decode the payload on metric datatype: name: "WriteableBoolean2" timestamp: 1698885026000 is_null: false boolean_value: true .

Which then causes the Transmission module to fail as well:

java.lang.NullPointerException: Cannot invoke "org.eclipse.tahu.message.model.Metric.getName()" because "metric" is null
at com.cirruslink.mqtt.transmission.gateway.sparkplug.SparkplugMqttCallback$MessageArrivedWorker.run(SparkplugMqttCallback.java:262)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

Would that be a bug in the Tahu lib?

Ah sorry - I misunderstood. I thought you were saying that Exception came from MQTT Engine. I actually fixed this on the develop branch of Tahu very recently here: Added EdgeNodeMetricMaps class for handling datatype lookups in Edge Nodes by wes-johnson · Pull Request #320 · eclipse/tahu · GitHub. This will be included in the next release of Transmission 4.0.19 (likely early next week).

It is already in the nightly build of Transmission here: Nightly Module Builds - MQTT Modules for Ignition 8.x - Confluence

Thanks Wes! I can confirm that the nightly build doesn’t fail when parsing the CMD and I can see the change reflected in the tag!

This is unrelated to my original question but I did notice that when the Tahu Host Application requests a rebirth for the edge node (either sending that explicitly as per the example in the README or as part of the ordering mechanism) there’s an Exception in the Transmission module with:

java.lang.NullPointerException: Cannot invoke "org.eclipse.tahu.message.model.MetricDataType.toIntValue()" because the return value of "org.eclipse.tahu.model.MetricDataTypeMap.getMetricDataType(String)" is null
at org.eclipse.tahu.message.SparkplugBPayloadDecoder.getMetricValue(SparkplugBPayloadDecoder.java:228)
at org.eclipse.tahu.message.SparkplugBPayloadDecoder.convertMetric(SparkplugBPayloadDecoder.java:123)
at org.eclipse.tahu.message.SparkplugBPayloadDecoder.buildFromByteArray(SparkplugBPayloadDecoder.java:84)
at org.eclipse.tahu.message.SparkplugBPayloadDecoder.buildFromByteArray(SparkplugBPayloadDecoder.java:56)
at com.cirruslink.mqtt.transmission.gateway.sparkplug.SparkplugMqttCallback$MessageArrivedWorker.run(SparkplugMqttCallback.java:241)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

The payload of the CMD sent there is

Message [topic=spBv1.0/JoseLocal/NCMD/E0, payload=SparkplugBPayload [timestamp=1698934276000, metrics=[Metric [name=Node Control/Rebirth, alias=null, timestamp=1698934276000, dataType=Boolean, isHistorical=null, isTransient=null, metaData=null, properties=null, value=true, isNull=false]], seq=null, uuid=null, body=null]]

This was a bug. I has been fixed in the nightly module and will be included in the v4.0.19 version of the module.