Node-RED is a good fit on an edge PC or gateway: it can read Modbus, S7, EtherNet/IP and OPC UA with community nodes and post the values to InduQT. The flow below runs inject → function → http request → debug.
Set up
- Create the site and machines
Create them in the app or with
POST /v1/sitesandPOST /v1/machines. Set each machine'sexternalIdto the name your source uses (for exampleB-101), so messages can address it without knowing InduQT ids. - Create an API key
In Settings → API keys, create a key with the
ingestscope, limited to the site the data belongs to. See site-scoped keys. - Give Node-RED the key
Set
INDUQT_API_KEYin the Node-RED process environment, so it is not stored in the flow:docker run -e INDUQT_API_KEY=iq_live_… nodered/node-red,Environment=INDUQT_API_KEY=…in a systemd unit, or export it beforenode-redstarts. The function node reads it withenv.get('INDUQT_API_KEY'). - Import the flow
Menu → Import, paste the JSON, Import, then Deploy.
- Wire in your tags
Replace the simulated value in Build reading with your tag, and change
B-101to your machine'sexternalId. The debug node showsstatusCode200 andacceptedfor each post.
Flow: post to the ingest API
[
{
"id": "iq_tab_http",
"type": "tab",
"label": "InduQT ingest",
"disabled": false,
"info": "Posts a reading to the InduQT ingest API every 5 seconds. Set INDUQT_API_KEY in the Node-RED environment."
},
{
"id": "iq_inject",
"type": "inject",
"z": "iq_tab_http",
"name": "Every 5 s",
"props": [
{
"p": "payload"
}
],
"repeat": "5",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 130,
"y": 100,
"wires": [
[
"iq_build"
]
]
},
{
"id": "iq_build",
"type": "function",
"z": "iq_tab_http",
"name": "Build reading",
"func": "// Replace the simulated value with your tag, e.g. msg.payload from an S7, Modbus or OPC UA node.\nconst steamPressure = Number((110 + Math.random() * 5).toFixed(1));\n\nmsg.headers = {\n 'Authorization': 'Bearer ' + env.get('INDUQT_API_KEY'),\n 'Content-Type': 'application/json'\n};\nmsg.payload = {\n machine: 'B-101',\n values: { steam_pressure: steamPressure }\n};\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 100,
"wires": [
[
"iq_post"
]
]
},
{
"id": "iq_post",
"type": "http request",
"z": "iq_tab_http",
"name": "POST /v1/ingest",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "https://api.induqt.ai/v1/ingest",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 530,
"y": 100,
"wires": [
[
"iq_debug"
]
]
},
{
"id": "iq_debug",
"type": "debug",
"z": "iq_tab_http",
"name": "InduQT response",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "true",
"targetType": "full",
"statusVal": "statusCode",
"statusType": "msg",
"x": 750,
"y": 100,
"wires": []
}
]The function node builds this request:
{
"method": "POST",
"headers": {
"Authorization": "Bearer iq_live_…",
"Content-Type": "application/json"
},
"payload": {
"machine": "B-101",
"values": {
"steam_pressure": 112.4
}
}
}Every 5 s is fine for one machine. For many tags, collect them in one function (or a join node) and send a single request with several values, or a readings batch; see the ingest API.
Alternative: publish over MQTT
If the site already has a broker that InduQT subscribes to through an MQTT connector, publish there instead. With topic filter plant/+/telemetry and machineFrom: "topic", topicLevel: 1, the message below lands on machine B-101. Set the broker host, port and credentials in the Plant broker config node after import.
[
{
"id": "iq_tab_mqtt",
"type": "tab",
"label": "InduQT over MQTT",
"disabled": false,
"info": "Publishes readings to plant/B-101/telemetry. Add an InduQT MQTT connector with topic plant/+/telemetry and machineFrom \"topic\", topicLevel 1."
},
{
"id": "iq_mqtt_inject",
"type": "inject",
"z": "iq_tab_mqtt",
"name": "Every 5 s",
"props": [
{
"p": "payload"
}
],
"repeat": "5",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 130,
"y": 100,
"wires": [
[
"iq_mqtt_build"
]
]
},
{
"id": "iq_mqtt_build",
"type": "function",
"z": "iq_tab_mqtt",
"name": "Build message",
"func": "// Replace the simulated value with your tag.\nmsg.topic = 'plant/B-101/telemetry';\nmsg.payload = {\n ts: new Date().toISOString(),\n steam_pressure: Number((110 + Math.random() * 5).toFixed(1))\n};\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 100,
"wires": [
[
"iq_mqtt_out"
]
]
},
{
"id": "iq_mqtt_out",
"type": "mqtt out",
"z": "iq_tab_mqtt",
"name": "plant/B-101/telemetry",
"topic": "",
"qos": "1",
"retain": "false",
"respTopic": "",
"contentType": "application/json",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "iq_broker",
"x": 540,
"y": 100,
"wires": []
},
{
"id": "iq_broker",
"type": "mqtt-broker",
"name": "Plant broker",
"broker": "broker.example.com",
"port": "8883",
"tls": "",
"clientid": "",
"autoConnect": true,
"usetls": true,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
| 401 with Bearer undefined | INDUQT_API_KEY is not in the Node-RED environment. Set it and restart Node-RED; redeploying is not enough. |
| 400 Bad Request | The body is not JSON or values are not numbers. Check that the function sets msg.payload to an object and the http request node returns a parsed object. |
| 404 or rejected readings | The machine name is not an externalId at the key's site. Use a site-scoped key, add siteId to the payload, or set autoCreate: true. |
| Certificate errors behind a proxy | A TLS-inspecting proxy needs its CA trusted by Node.js (NODE_EXTRA_CA_CERTS) or a TLS config on the http request node. |
| Gaps after network outages | Buffer messages while offline (for example with node-red-contrib-queue-gate) and resend: repeated readings overwrite, so resending is safe. |