Issues with ignition and ignition designer while using Node Red

Hello. I setup the ignition broker and I was able to publish and subscribe messages through the same by making use of a NODE RED program. The issue is while I am able to observe the published and subscribed messages in NODE RED and MQTT.fx, I do not see the payload metrics being identified in ignition web interface tags or in the ignition designer. My topic is in the correct format as specified b SpB. Please help me with this and thank you.

Are there any errors/warnings in the logs? Also, are you conducting Sparkplug lifecycles as well with BIRTH, NDATA, DDATA, etc publishes (i.e. there is more than just one topic involved)?

EDIT: I also presume that you’ve got both MQTT Distributor and MQTT Engine deployed to your gateway?

1 Like

MQTT Engine, Transmission and Distribution modules are all setup. I got an example code from cirrulink website

/********************************************************************************

  • Copyright (c) 2016-2018 Cirrus Link Solutions and others
  • This program and the accompanying materials are made available under the
  • terms of the Eclipse Public License 2.0 which is available at
  • Eclipse Public License 2.0 | The Eclipse Foundation.
  • SPDX-License-Identifier: EPL-2.0
  • Contributors:
  • Cirrus Link Solutions - initial implementation

When I made use of this in node red , I could see the devices appear in ignition designer along in a heirarchial manner , the sub folders in the edge node include node info, node control, input and outputs folder .

However when I try to connect an SpB publisher block to a basin .json code in SpB, with the topic having a message type of NDATA only, the device does not show up in ignition designer or tags.

Also, when i tried to replicate the code from cirrus link , the device shows up in ignition designer but with only two subfolders - node info and node control. I don’k know if I could share screenshots in here. Id be happy to send you an email with screenshots if you’re okay with it.

I was able to get a device going on my test bench with the following flow:

Here is the JSON of the flow if you’d like to try it out:

Node-RED Example Sparkplug Flow
[
    {
        "broker": "tcp://gateway",
        "clientid": "NodeREDSimpleEdgeNode",
        "credentials": {},
        "edgenode": "Edge Node RED",
        "enablecache": "false",
        "groupid": "My MQTT Group",
        "id": "21cb23aca25f8ff0",
        "name": "",
        "password": "changeme",
        "port": "1883",
        "publishdeath": "true",
        "type": "sparkplug",
        "user": "admin",
        "version": "spBv1.0",
        "wires": [
            [
                "ef55783b857bf953"
            ]
        ],
        "x": 540,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    },
    {
        "active": true,
        "complete": "true",
        "console": false,
        "id": "5a3e31b2cb00bee4",
        "name": "sparkplug debug",
        "statusType": "auto",
        "statusVal": "",
        "targetType": "full",
        "tosidebar": true,
        "tostatus": false,
        "type": "debug",
        "wires": [],
        "x": 750,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    },
    {
        "finalize": "",
        "func": "// Capture device name\nglobal.set(\"deviceId\", \"PLC 1\")\n\nvar payload = {\n    \"timestamp\": msg.payload,\n}\nvar metrics = []\nmetrics.push({\n    \"name\": \"CurrentSeconds\",\n    \"value\": new Date().getSeconds(),\n    \"type\": \"int32\"\n})\npayload[\"metrics\"] = metrics\nmsg.payload = payload\nmsg.topic = `${global.get(\"deviceId\")}/DBIRTH`\nflow.set(\"running\", true)\nreturn msg;\n",
        "id": "744bbf99e693c7b5",
        "initialize": "",
        "libs": [],
        "name": "DBIRTH",
        "noerr": 0,
        "outputs": 1,
        "type": "function",
        "wires": [
            [
                "f757327680907b72"
            ]
        ],
        "x": 300,
        "y": 260,
        "z": "f6f2187d.f17ca8"
    },
    {
        "id": "8bff98c79de5e59f",
        "links": [
            "5eebf0852ffa9b5c"
        ],
        "name": "Rebirth",
        "type": "link in",
        "wires": [
            [
                "744bbf99e693c7b5"
            ]
        ],
        "x": 55,
        "y": 200,
        "z": "f6f2187d.f17ca8"
    },
    {
        "id": "5eebf0852ffa9b5c",
        "links": [
            "8bff98c79de5e59f"
        ],
        "mode": "link",
        "name": "Rebirth",
        "type": "link out",
        "wires": [],
        "x": 845,
        "y": 200,
        "z": "f6f2187d.f17ca8"
    },
    {
        "checkall": "true",
        "id": "110b822bcda4a472",
        "name": "Route Outputs",
        "outputs": 1,
        "property": "topic",
        "propertyType": "msg",
        "repair": false,
        "rules": [
            {
                "t": "eq",
                "v": "rebirth",
                "vt": "str"
            }
        ],
        "type": "switch",
        "wires": [
            [
                "5eebf0852ffa9b5c"
            ]
        ],
        "x": 660,
        "y": 200,
        "z": "f6f2187d.f17ca8"
    },
    {
        "crontab": "",
        "id": "46bec1e9c2b3a746",
        "name": "Update",
        "once": true,
        "onceDelay": 0.1,
        "payload": "",
        "payloadType": "date",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "running",
                "v": "running",
                "vt": "flow"
            }
        ],
        "repeat": "1",
        "topic": "",
        "type": "inject",
        "wires": [
            [
                "09e10fa13e6c9609"
            ]
        ],
        "x": 100,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    },
    {
        "finalize": "",
        "func": "if (!msg.running) {\n    return null;\n}\nvar payload = {\n    \"timestamp\": msg.payload\n}\nvar metrics = []\nmetrics.push({\n    \"name\": \"CurrentSeconds\",\n    \"value\": new Date().getSeconds(),\n    \"type\": \"int32\"\n})\npayload[\"metrics\"] = metrics\nmsg.payload = payload\nmsg.topic = `${global.get(\"deviceId\")}/DDATA`\nreturn msg;\n\n",
        "id": "09e10fa13e6c9609",
        "initialize": "",
        "libs": [],
        "name": "DDATA",
        "noerr": 0,
        "outputs": 1,
        "type": "function",
        "wires": [
            [
                "21cb23aca25f8ff0"
            ]
        ],
        "x": 310,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    },
    {
        "finalize": "",
        "func": "var payload = {\n    \"timestamp\": msg.payload,\n}\nmsg.payload = payload\nmsg.topic = `${global.get(\"deviceId\")}/DDEATH`\nflow.set(\"running\", false)\nreturn msg;\n",
        "id": "1d621881bee11c4e",
        "initialize": "",
        "libs": [],
        "name": "DDEATH",
        "noerr": 0,
        "outputs": 1,
        "type": "function",
        "wires": [
            [
                "f757327680907b72"
            ]
        ],
        "x": 300,
        "y": 380,
        "z": "f6f2187d.f17ca8"
    },
    {
        "crontab": "",
        "id": "08421e63bb112d1e",
        "name": "Stop",
        "once": false,
        "onceDelay": 0.1,
        "payload": "",
        "payloadType": "date",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "reset",
                "v": "true",
                "vt": "str"
            }
        ],
        "repeat": "",
        "topic": "",
        "type": "inject",
        "wires": [
            [
                "1d621881bee11c4e"
            ]
        ],
        "x": 90,
        "y": 380,
        "z": "f6f2187d.f17ca8"
    },
    {
        "crontab": "",
        "id": "d95cf0a12dcc1815",
        "name": "Start",
        "once": false,
        "onceDelay": 0.1,
        "payload": "",
        "payloadType": "date",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "topic": "",
        "type": "inject",
        "wires": [
            [
                "744bbf99e693c7b5"
            ]
        ],
        "x": 90,
        "y": 260,
        "z": "f6f2187d.f17ca8"
    },
    {
        "id": "ef55783b857bf953",
        "type": "junction",
        "wires": [
            [
                "5a3e31b2cb00bee4",
                "110b822bcda4a472"
            ]
        ],
        "x": 620,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    },
    {
        "id": "f757327680907b72",
        "type": "junction",
        "wires": [
            [
                "21cb23aca25f8ff0"
            ]
        ],
        "x": 440,
        "y": 320,
        "z": "f6f2187d.f17ca8"
    }
]

I hadn’t spooled up Node-RED in a while, and I think I had much better luck with this latest revision of the node-red-contrib-sparkplug node than the last time I tried a few years ago.

1 Like

Thank you @kcollins1 . I will try this now.

This is .json of the current flow I have

Node-RED Flow
[
    {
        "id": "b7be3d2934d3a264",
        "type": "tab",
        "label": "Flow 3",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "11dd3842bd9bf80c",
        "type": "inject",
        "z": "b7be3d2934d3a264",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "timestamp",
        "payload": "",
        "payloadType": "date",
        "x": 400,
        "y": 240,
        "wires": [
            [
                "6d0acaf5b04fe652"
            ]
        ]
    },
    {
        "id": "dbb2a1a41483d127",
        "type": "sparkplug",
        "z": "b7be3d2934d3a264",
        "name": "",
        "broker": "tcp://localhost",
        "port": "1883",
        "clientid": "NodeREDSimpleEdgeNode",
        "groupid": "Sparkplug Devices",
        "edgenode": "Node-RED Edge Node",
        "version": "spBv1.0",
        "enablecache": "false",
        "publishdeath": "true",
        "user": "admin",
        "password": "__PWRD__",
        "x": 780,
        "y": 320,
        "wires": [
            [
                "6d0acaf5b04fe652"
            ]
        ]
    },
    {
        "id": "6d0acaf5b04fe652",
        "type": "function",
        "z": "b7be3d2934d3a264",
        "name": "Emulated Device51",
        "func": "/********************************************************************************\n * Copyright (c) 2016-2018 Cirrus Link Solutions and others\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License 2.0 which is available at\n * http://www.eclipse.org/legal/epl-2.0.\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n *   Cirrus Link Solutions - initial implementation\n ********************************************************************************/\nvar deviceId = \"Emulated Device51\"\n    hwVersion = \"Emulated Hardware\",\n    swVersion = \"v1.0.0\",\n\n/*\n * Generates a random integer\n */\nrandomInt = function() {\n    return 1 + Math.floor(Math.random() * 10);\n};\n\ngetTopic = function(type) {\n    return deviceId + \"/\" + type;\n}\n\n/*\n * Returns the full birth payload for the emulated device\n */\ngetBirthPayload = function() {\n    return {\n        \"timestamp\" : new Date().getTime(),\n        \"metrics\" : [\n            { \n                \"name\" : \"my_boolean\", \n                \"value\" : Math.random() > 0.5, \n                \"type\" : \"boolean\",\n                \"properties\" : {\n                    \"EngUnit\" : {\n                        \"value\" : \"ChadsUnits\",\n                        \"type\" : \"string\"\n                    }\n                } \n            },\n            { \"name\" : \"my_double\", \"value\" : Math.random() * 0.123456789, \"type\" : \"double\" },\n            { \"name\" : \"my_float\", \"value\" : Math.random() * 0.123, \"type\" : \"float\" },\n            { \"name\" : \"my_int\", \"value\" : randomInt(), \"type\" : \"int\" },\n            { \"name\" : \"my_long\", \"value\" : randomInt() * 214748364700, \"type\" : \"long\" },\n            { \"name\" : \"Inputs/0\", \"value\" :  true, \"type\" : \"boolean\" },\n            { \"name\" : \"Inputs/1\", \"value\" :  0, \"type\" : \"int\" },\n            { \"name\" : \"Inputs/2\", \"value\" :  1.23, \"type\" : \"float\" },\n            { \"name\" : \"Outputs/0\", \"value\" :  true, \"type\" : \"boolean\" },\n            { \"name\" : \"Outputs/1\", \"value\" :  0, \"type\" : \"int\" },\n            { \"name\" : \"Outputs/2\", \"value\" :  1.23, \"type\" : \"float\" },\n            { \"name\" : \"Properties/hw_version\", \"value\" :  hwVersion, \"type\" : \"string\" },\n            { \"name\" : \"Properties/sw_version\", \"value\" :  swVersion, \"type\" : \"string\" }\n        ]\n    };\n};\n\n/*\n * Returns the death payload for the emulated device\n */\ngetDeathPayload = function() {\n    return {\n        \"timestamp\" : new Date().getTime()\n    };\n};\n\n/*\n * Returns the data payload for the device\n */\ngetDataPayload = function(msg) {\n    return {\n        \"timestamp\" : msg.payload.timestamp !== undefined ? msg.payload.timestamp : new Date().getTime(),\n        \"metrics\" : [\n            { \"name\" : \"my_boolean\", \"value\" : Math.random() > 0.5, \"type\" : \"boolean\" },\n            { \"name\" : \"my_double\", \"value\" : Math.random() * 0.123456789, \"type\" : \"double\" },\n            { \"name\" : \"my_float\", \"value\" : Math.random() * 0.123, \"type\" : \"float\" },\n            { \"name\" : \"my_int\", \"value\" : randomInt(), \"type\" : \"int\" },\n            { \"name\" : \"my_long\", \"value\" : randomInt() * 214748364700, \"type\" : \"long\" }\n            ]\n    };\n};\n\n/*\n * Process the incoming message by topic.  The following actions will be taked based on the incoming message topic:\n * \n * topic = deviceId\n *   The emulated device is receiving a DCMD. Process the incoming (writable) metrics and publish all changed metrics.\n *   \n * topic = rebirth\n *   A rebirth command is requested, publish the devices full metrics.\n *   \n * topic = death\n *   Publish a device death message indicating that the device has gone offline.\n *   \n * topic = timestamp\n *   Publish the default device data payload using the new timestamp.\n */\nif (msg.topic === deviceId) {\n    var metrics = msg.payload.metrics,\n        inboundMetricMap = {},\n        outboundMetric = [],\n        outboundPayload;\n\n    console.log(deviceId + \" received 'DCMD' command\");\n\n    // Loop over the metrics and store them in a map\n    if (metrics !== undefined && metrics !== null) {\n        for (var i = 0; i < metrics.length; i++) {\n            var m = metrics[i];\n            inboundMetricMap[m.name] = m.value;\n        }\n    }\n\n    if (inboundMetricMap[\"Outputs/0\"] !== undefined && inboundMetricMap[\"Outputs/0\"] !== null) {\n        console.log(\"Outputs/0: \" + inboundMetricMap[\"Outputs/0\"]);\n        outboundMetric.push({ \"name\" : \"Inputs/0\", \"value\" : inboundMetricMap[\"Outputs/0\"], \"type\" : \"boolean\" });\n        outboundMetric.push({ \"name\" : \"Outputs/0\", \"value\" : inboundMetricMap[\"Outputs/0\"], \"type\" : \"boolean\" });\n        console.log(\"Updated value for Inputs/0 \" + inboundMetricMap[\"Outputs/0\"]);\n    } else if (inboundMetricMap[\"Outputs/1\"] !== undefined && inboundMetricMap[\"Outputs/1\"] !== null) {\n        console.log(\"Outputs/1: \" + inboundMetricMap[\"Outputs/1\"]);\n        outboundMetric.push({ \"name\" : \"Inputs/1\", \"value\" : inboundMetricMap[\"Outputs/1\"], \"type\" : \"int\" });\n        outboundMetric.push({ \"name\" : \"Outputs/1\", \"value\" : inboundMetricMap[\"Outputs/1\"], \"type\" : \"int\" });\n        console.log(\"Updated value for Inputs/1 \" + inboundMetricMap[\"Outputs/1\"]);\n    } else if (inboundMetricMap[\"Outputs/2\"] !== undefined && inboundMetricMap[\"Outputs/2\"] !== null) {\n        console.log(\"Outputs/2: \" + inboundMetricMap[\"Outputs/2\"]);\n        outboundMetric.push({ \"name\" : \"Inputs/2\", \"value\" : inboundMetricMap[\"Outputs/2\"], \"type\" : \"float\" });\n        outboundMetric.push({ \"name\" : \"Outputs/2\", \"value\" : inboundMetricMap[\"Outputs/2\"], \"type\" : \"float\" });\n        console.log(\"Updated value for Inputs/2 \" + inboundMetricMap[\"Outputs/2\"]);\n    }\n\n    outboundPayload = {\n            \"timestamp\" : new Date().getTime(),\n            \"metrics\" : outboundMetric\n    };\n\n    return {\n        \"topic\" : getTopic(\"DDATA\"),\n        \"payload\" : outboundPayload\n    };\n    \n} else if (msg.topic === \"rebirth\") {\n    console.log(deviceId + \" received 'rebirth' command\");\n    return {\n        \"topic\" : getTopic(\"DBIRTH\"),\n        \"payload\" : getBirthPayload()\n    };\n    \n} else if (msg.topic === \"timestamp\"){\n    console.log(deviceId + \" received 'timestamp' message\");\n    return {\n        \"topic\" : getTopic(\"DDATA\"),\n        \"payload\" : getDataPayload(msg)\n    };\n} else if (msg.topic === \"death\"){\n    console.log(deviceId + \" received 'timestamp' message\");\n    return {\n        \"topic\" : getTopic(\"DDEATH\"),\n        \"payload\" : getDeathPayload(msg)\n    };\n}\n\nreturn null;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 770,
        "y": 240,
        "wires": [
            [
                "dbb2a1a41483d127",
                "59e460dce64e8443"
            ]
        ]
    },
    {
        "id": "59e460dce64e8443",
        "type": "debug",
        "z": "b7be3d2934d3a264",
        "name": "debug 8",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1140,
        "y": 240,
        "wires": []
    }
]

It gives me an output in the ignition designer as follows
image

When I tried the .json you’ve provided above, it gives me the following output on ignition designer
image

However in both cases, I am losing out on the metrics like I do not have an inputs and outputs folder in there. Could you help me with it ? Thank you.

The screenshots you show look like what I see as well… It looks like there are the same properties:

2022-07-27 at 5.13 PM

The logic in the example (from Cirrus Link) just propagates the outputs to the inputs. Once you unblock outgoing node/device commands (in MQTT Engine configuration), that seems to work too:

ScreenFlow

EDIT: the example flow I provided above only has “CurrentSeconds” tag under that “PLC 1” folder, no “Inputs” and “Outputs” expected.

1 Like

In most cases Ignition web interface shows up the data quality of tags as shown in the picture below.


This is a representation of the data quality and is a very critical factor in determining the credibility of the data.

Also, these values get updated in real time.

In the example above, the current seconds metric updates itself every second. But the ignition designer only updates that every 6 secs. Could you please help me with it? I am trying to see if the value would be updated in real time. I appreciate your time and thank you.

The update rate of the tags in Tag Browser within Designer can vary for a number of reasons. If you’d like more confidence on when values are coming into the tags, perhaps setup Tag History on it. You can then query tag history for the values of that tag to see that the tag itself should be receiving updates each second.

tag-history

1 Like