
Integrating Node-RED with ControlCom Connect: Simplify Your IoT Development
Wiring Node-RED into ControlCom Connect over MQTT-TLS — client IDs, cert formats, payload shape, and gotchas from real customer gateways.
The pitch for Node-RED is simple: wire boxes together, ship a working IoT prototype before lunch. The reality is more interesting. Once you start pushing real device data into ControlCom Connect over MQTT-TLS, you run into the same handful of gotchas every time — client IDs, certificate formats, context persistence, payload reshaping. This post is what we wish we’d known the first time.
When Node-RED is the right tool
We reach for Node-RED on two kinds of jobs:
- Edge protocol translation. A customer has a Modbus PLC, a Siemens S7, or a serial-only meter, and we need to push tags into Connect without writing custom firmware. A Node-RED instance on a Raspberry Pi or industrial PC does this in an afternoon.
- Prototype-to-demo loop. Sales needs a live dashboard for a meeting on Thursday and the customer has nothing deployable yet. Mock a flow, point it at a Connect device, and the data lands in their tenant.
Where we don’t reach for it: anything past ~20 devices per gateway, anything with hard real-time deadlines, anything we’d need to scale horizontally. Node-RED is a flow runtime, not a stream processor.
MQTT setup, in the order things bite you
The Connect broker is on mqtts://mqtt.controlcomtech.com:8883. Plain 1883 isn’t an option — TLS is required, and you authenticate with a per-device certificate, not a username and password.
Open the mqtt-broker config node in Node-RED. From there:
1. Client ID
MQTT brokers will silently disconnect you if two clients connect with the same Client ID — the broker keeps the newer connection and kicks the old one off. If your flow keeps reconnecting every few seconds for no obvious reason, you’ve cloned a flow without changing the Client ID. We set ours to ${deviceId}-nodered-${hostname} so a moved gateway doesn’t collide with the one it replaced.
2. TLS config
Connect issues each device a certificate as three files: device.crt, device.key, and ca.crt. Node-RED’s TLS config node wants all three. A few traps:
- The cert and key must be PEM, not DER or PKCS12. If your export tool gave you a
.pfxor.p12, run it throughopenssl pkcs12 -in cert.pfx -out cert.pem -nodesfirst. - Leave “Verify server certificate” on. The CA file is what makes that work — without it Node-RED falls back to the system trust store, which may not include our intermediate CA.
- Leave “Use ALPN” off unless the docs explicitly say otherwise. It’s almost never what you want on a self-hosted gateway.
3. Keep-alive
Default is 60s. Cellular gateways, or anything behind carrier-grade NAT, will drop the TCP connection after about five minutes of silence and you’ll see ghosted device state in Connect. Drop keep-alive to 30s on any link you don’t control end-to-end.
The payload shape
Connect ingests this JSON:
{
"dataArray": [
{ "key": "temperature_c", "value": 24.5 },
{ "key": "pressure_kpa", "value": 101.3 }
]
}
Two things that aren’t obvious from the spec:
valueis typed. A numeric24.5and a string"24.5"land as different point types in Connect, and once a point exists with a type, sending the other shape will be rejected. Pick one in your Function node and stick with it.- Batch your readings. The broker will accept a message every few ms, but the Connect ingest side is happier with related readings batched into the same
dataArray. Aim for one publish per scan cycle, not one publish per tag.
A minimal reshape Function node:
const readings = msg.payload;
msg.payload = {
dataArray: Object.entries(readings).map(([key, value]) => ({
key,
value: typeof value === 'number' ? value : Number(value),
})),
};
msg.topic = `devices/${flow.get('deviceId')}/data`;
return msg;
The context-persistence trap
Node-RED’s flow context and global context are in-memory by default. If you’re using context to track last-known values, debounce noisy sensors, or hold counters across messages, a restart wipes all of it.
To make context survive restarts, add a contextStorage block to settings.js:
contextStorage: {
default: { module: 'localfilesystem' },
}
Then context.get('lastValue', 'default') reads from disk instead of memory. Required for anything you’d call production.
Things we’d avoid
- Don’t run Node-RED on the same host as your application unless you have to. If the flow editor is exposed, anyone on the LAN can deploy code to your device. Run it on a dedicated host, behind auth, or both.
- Don’t use QoS 2. It’s expensive, the broker session state is fragile, and for telemetry you almost never need exactly-once delivery. QoS 1 with idempotent keys on the Connect side is the right default.
- Don’t build state machines out of nodes. If you need more than “if value > threshold, publish alert,” reach for a Function node with real JavaScript. Long visual chains look clean for ten minutes and become unmaintainable after a week.
Where to go next
The full setup walkthrough — certificate download flow, screenshots of every config node, and the example flow JSON you can import directly — lives in the docs:
Read the Complete Node-RED Integration Guide →
If you’re already running Node-RED and hit something the guide doesn’t cover, reach out — odds are we’ve debugged the same thing on a customer gateway.