1. System Overview and Data Flow
The integration described in this article captures non-contact surface temperature data from a Melexis MLX90614 infrared thermometer on the internal I²C bus of a Siemens SIMATIC IOT2040, parses the result inside Node-RED, and forwards the telemetry to MindSphere as a time series. The IOT2040 is an Intel Quark x1020 based industrial IoT gateway that ships with the SIMATIC IOT2000 SD-Card Example Image containing Node-RED, the iot2000setup tool, and the libmraa I/O library. The MLX90614 is a single-pixel thermopile sensor with integrated signal conditioning, factory-calibrated SMBus-compatible I²C interface, and an object temperature range of -70 °C to +380 °C on the BCC variant. The sensor combines a thermopile detector with an integrated ASIC; both chip and object temperature are returned in Kelvin, scaled to 0.02 K per LSB.
The key challenge addressed here is that the IOT2040's internal I²C bus is not exposed as a standard Linux /dev/i2c-N node in the default state, and Node-RED does not include a generic I²C read node targeting the Quark/Yocto stack. The node-red-contrib-mlx-sensor contribution is the supported workaround: it uses the cross-platform i2c-bus npm package and decodes the MLX90614 register map directly, returning msg.payload.ambient and msg.payload.object in degrees Celsius.
2. Prerequisites
| Item | Specification | Notes |
|---|---|---|
| SIMATIC IOT2040 | 6ES7647-0AA00-1YA2 (or -1YA3, -0AA01) | Intel Quark x1020 @ 400 MHz, 1 GB DDR3, 2× GbE, 2× USB 2.0, 1× internal Arduino-compatible 26-pin header |
| Example Image | SIMATIC IOT2000 SD-Card Example Image V2.6.1 (or V3.x) | Includes Node-RED, iot2000setup, libmraa, libupm, node.js, openssh, i2c-tools |
| MLX90614 module | CJMCU-MLX90614, Sparkfun SEN-09570, Adafruit PID 1748, or bare MLX90614BAA | 3.3 V version preferred; 5 V breakouts with on-board LDO accept 3.3 V input |
| Wiring | 4-wire female-female DuPont, 22-26 AWG, < 100 mm | Short leads stay within the SMBus 300 ns rise-time limit at 100 kHz |
| Pull-up resistors | 2.2 kΩ to 4.7 kΩ on SDA and SCL to VCC | Most MLX90614 carrier boards include the resistors; confirm with the board schematic |
| Network | TCP/IPv4, DNS, NTP from IOT2040 to MindSphere tenant | MindSphere requires NTP-skew < 60 s; configure systemd-timesyncd or ntpd |
| MindSphere access | MindConnect Element credential, tenant URL, asset model | Provisioned through the MindSphere Cockpit MindConnect page |
3V3 pin. The MLX90614 operates from 2.6 V to 3.6 V. Do not connect a 5 V-only MLX90614 carrier to the 3.3 V rail without confirming the on-board LDO accepts 3.3 V input; the CJMCU-MLX90614 ships with an AP2112K LDO that requires ≥ 3.0 V and works from the 3V3 pin.3. Hardware Wiring: IOT2040 Internal Header to MLX90614
The SIMATIC IOT2040 exposes an internal 26-pin header following the Arduino Uno pinout convention. The I²C pins are located on the AREF/IOREF group of the 26-pin connector. The relevant signal pins are listed below; the SDA / SCL pair appears on the analog header at A4 / A5, which the IOT2040 firmware reassigns to the SoC's I2C0 controller.
| IOT2040 26-pin pin | Arduino name | MLX90614 carrier pin | Function |
|---|---|---|---|
| 4 | 3V3 | VCC (or VIN on CJMCU) | +3.3 V supply to sensor |
| 6 | GND | GND | Common ground |
| 7 | GND | GND | Common ground (second bond) |
| 13 | A4 / SDA | SDA | I²C data (with pull-up to 3V3) |
| 14 | A5 / SCL | SCL | I²C clock (with pull-up to 3V3) |
- Power down the IOT2040:
sudo shutdown -h now. - Open the enclosure and identify the 26-pin internal header near the Quark module.
- Connect 3V3 → VCC, GND → GND, A4 (SDA) → SDA, and A5 (SCL) → SCL using DuPont jumpers.
- Verify that the carrier board has 2.2 kΩ – 4.7 kΩ pull-ups on SDA and SCL tied to its VCC rail. The CJMCU-MLX90614, Sparkfun SEN-09570, and Adafruit 1748 all include these resistors.
- Close the enclosure, re-attach the SD card, and power up.
4. Enabling I²C and Verifying the Sensor on the Bus
By default the IOT2000 Example Image ships with I²C and SPI disabled in the kernel device tree to free the pins for the user application. The iot2000setup tool toggles the device tree overlay.
# Connect over SSH or via the serial console
ssh root@<iot2040-ip>
iot2000setup
Inside the TUI:
- Select Peripherals → I2C.
- Toggle Enable I2C0 to Yes.
- Optional: toggle Enable SPI if you plan to add a display.
- Select Save and Reboot.
After reboot, verify that the device node exists:
root@iot2040:~# ls -l /dev/i2c-0
crw-rw---- 1 root i2c 89, 0 Jan 1 00:12 /dev/i2c-0
root@iot2040:~# dmesg | grep -i i2c
[ 1.832] i2c /dev entries driver
[ 2.014] i2c-designware-pci 0000:00:08.0: I2C bus registered
/dev/i2c-0 is missing after reboot, the i2c-dev module is not loaded. Run modprobe i2c-dev and persist it via /etc/modules-load.d/i2c-dev.conf.The IOT2000 Example Image includes i2c-tools with i2cdetect, i2cget, and i2cset. Confirm the sensor is wired correctly before touching Node-RED:
root@iot2040:~# i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- 5a -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
0x5A is the factory default MLX90614 7-bit address. If you see -- at that position, the most common causes are:
- Wiring error (SDA / SCL swapped, or 5 V applied to a 3.3 V sensor).
- Missing pull-up resistors on the carrier board.
- I²C not enabled in
iot2000setup. - Sensor sleeping (the MLX90614 enters sleep on SCL low > 33 ms; toggle SCL to wake).
Read the raw object-temperature register to confirm a sane value:
root@iot2040:~# i2cget -y 0 0x5A 0x07 w
0x3A1A
root@iot2040:~# python3 -c "print((0x3A1A)*0.02 - 273.15)"
24.33
The MLX90614 returns temperature in 0.02 K per LSB. Decoding: 0x3A1A = 14874 × 0.02 = 297.48 K − 273.15 = 24.33 °C. A value near room temperature on a 3.3 V supply confirms the wiring, pull-ups, and address are correct. The companion register 0x06 is the chip (ambient) temperature and is normally within ±1 °C of the object temperature when the sensor faces a black-body target at ambient.
5. Installing the node-red-contrib-mlx-sensor Node
The node-red-contrib-mlx-sensor package wraps the i2c-bus npm module and exposes an mlx input node that scans the MLX90614 sub-address space (0x5A–0x5D) and emits a parsed payload. Installation is done from the IOT2040 shell:
cd ~/.node-red
npm install node-red-contrib-mlx-sensor
sudo systemctl restart node-red
Or, from the Node-RED editor:
- Open the editor at
http://<iot2040-ip>:1880. - Click the menu (☰) → Manage palette → Install.
- Search for
node-red-contrib-mlx-sensorand click Install. - Wait for the dependency tree (which includes
i2c-busandrxjs) to compile; the IOT2040 takes 30–60 s for a fresh install on the Quark core.
Verify the node appears:
root@iot2040:~/.node-red# ls node_modules | grep mlx
node-red-contrib-mlx-sensor
Restart Node-RED only — do not reboot the IOT2040 — and confirm the new node is listed in the left-hand palette under the MLX group.
node-red systemd unit access to /dev/i2c-0 via the i2c group. If you start Node-RED manually as root for debugging, the node will work even when group membership is missing. In production, run the unit: systemctl start node-red.6. Building the Temperature Flow
The minimal flow reads the MLX90614 every 5 s, attaches a timestamp, and pushes the value to a debug pane. Drop the following nodes onto the canvas:
-
mlx (from the MLX palette) — set
Busto/dev/i2c-0,Addressto0x5A,Intervalto5(seconds), and enable Read ambient + Read object. - function — wrap the value into a JSON envelope with ISO timestamp and topic.
- debug — display the JSON in the debug tab.
Paste this into the function node:
// Build a uniform message for downstream nodes
const ts = new Date().toISOString();
msg.payload = {
timestamp: ts,
source: "mlx90614",
bus: "/dev/i2c-0",
address: 0x5A,
ambient_c: msg.payload.ambient,
object_c: msg.payload.object
};
msg.topic = "iot/edge/temperature";
return msg;
The expected debug output should resemble:
{
"timestamp": "2024-03-12T14:22:08.412Z",
"source": "mlx90614",
"bus": "/dev/i2c-0",
"address": 90,
"ambient_c": 23.81,
"object_c": 24.27
}
Save the flow and click Deploy. Confirm that the debug tab updates every 5 s and that the values are within physical reason for the target surface. If ambient_c returns NaN, the bus is not enabled or the sensor is not responding — return to Section 4.
6.1 Threshold trigger pattern
To drive a relay or GPIO from a temperature trip point, add a switch node after the function block:
// switch node property = msg.payload.object_c
// case 1: > 60 (alarm HIGH)
// case 2: < 5 (alarm LOW)
Wire the HIGH output to a rpi-gpio out node (when the flow is moved to a Raspberry Pi) or to an mraa-backed GPIO pin on the IOT2040 via the node-red-contrib-mraa contribution.
7. Publishing to MindSphere
Two options are available for forwarding the JSON envelope to MindSphere.
7.1 MindConnect Node-RED node (recommended)
Siemens provides the node-red-contrib-mindconnect package, available from the Node-RED library and the Siemens MindSphere developer portal. Install it the same way as the MLX node:
cd ~/.node-red
npm install node-red-contrib-mindconnect
sudo systemctl restart node-red
Configure the mindconnect node with the agent's MindConnect onboarding credentials (issued from the MindSphere Cockpit → MindConnect page) and map the function output to the data point definition declared in the asset's aspect type.
7.2 Generic MQTT bridge
For tenants using the MindSphere MQTT broker, add an mqtt out node to the flow:
| Parameter | Value |
|---|---|
| Broker |
tcp://<tenant>.mindsphere.io:1883 (TLS: ssl://<tenant>.mindsphere.io:8883) |
| Client ID | Unique per IOT2040, e.g. iot2040-<serial>
|
| Topic | spBv1.0/<tenant>/DDATA/<asset>/<aspect> |
| QoS | 1 |
| Payload | The JSON object built in Section 6 |
timestamp, value, and qualityCode. Wrap ambient_c and object_c into two separate payloads with different data point names if you want both in the same aspect. MindSphere rejects payloads > 1 MB; the MLX envelope is < 200 bytes.8. Alternative Path: Johnny-Five with galileo-io
When the mlx-sensor node does not cover a derived use case — for example, triggering a GPIO on a temperature threshold inside the same flow — the historic workaround is the node-red-contrib-johnny-five runtime plus the galileo-io I/O plugin. This stack has two well-documented pitfalls on the IOT2040.
8.1 Install order
cd ~/.node-red
npm install node-red-contrib-johnny-five
npm install galileo-io johnny-five
On the IOT2040 (Quark x1020), the galileo-io package compiles its native binding firmata against MRAA. If the Yocto toolchain in the image is not the default, the install produces an ELIFECYCLE error and Node-RED refuses to start on the next boot:
npm ERR! [email protected] install: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! Failed at the [email protected] install script.
8.2 Recovery procedure
- Stop the unit:
systemctl stop node-red. - Roll back the partial install:
cd ~/.node-red && rm -rf node_modules/galileo-io node_modules/johnny-five && npm rebuild. - Reinstall with the Yocto SDK (if installed):
source /opt/crosscompile/environment-setup-i586-poky-linux npm install galileo-io --target_arch=ia32 --build-from-source - Restart:
systemctl start node-red.
If the rebuild still fails (the IOT2040 2.6.1 image does not ship with the cross-compile environment), abandon the Johnny-Five path and use the mlx-sensor node as documented in Section 5. The j5-gpio-out with embedded i2cRead call is fragile because the I²C address argument is parsed as a string and decimal/hex mismatch causes silent no-ops; the dedicated mlx-sensor node accepts both decimal and hex notation and validates the PEC byte.
8.3 Direct function-node access via i2c-bus
For engineers who prefer a single function node without a contributed package, add i2c-bus as a project dependency and read the MLX90614 directly:
const i2c = require("i2c-bus");
const bus = i2c.openSync(0);
// Read 16-bit object temperature (register 0x07)
const buf = Buffer.alloc(2);
bus.readI2cBlockSync(0x5A, 0x07, 2, buf);
const raw = (buf[0] | (buf[1] << 8)) & 0x7FFF;
const objectC = raw * 0.02 - 273.15;
msg.payload = { object_c: objectC, raw: raw };
return msg;
Run npm install i2c-bus inside ~/.node-red first. This pattern avoids any Node-RED contribution and is the most resilient for long-running edge deployments.
9. Emissivity and Accuracy Considerations
The MLX90614 leaves the factory with the emissivity register (sub-address 0x02) set to 0xFFFF, which decodes to ε = 1.0. This is correct only for a perfect black-body radiator. Common materials and their emissivity values at 25 °C are listed below; the sensor can be re-calibrated to these by writing the corresponding 16-bit value to 0x02:
| Material | Emissivity (ε) | Register value (hex) | Register value (dec) |
|---|---|---|---|
| Human skin | 0.98 | 0x3D70 | 15728 |
| Wood | 0.90 | 0x399A | 14746 |
| Concrete | 0.94 | 0x3C28 | 15400 |
| Polished aluminium | 0.05 | 0x051B | 1307 |
| Anodised aluminium | 0.77 | 0x3148 | 12616 |
| Stainless steel (rough) | 0.85 | 0x3666 | 13926 |
| Glass | 0.92 | 0x3AEA | 15082 |
| PCB FR4 (copper side) | 0.78 | 0x31EB | 12779 |
The MELXMLX90614 absolute accuracy is ±0.5 °C in the 0–50 °C object-temperature range and ±1.0 °C across the wider –70–+380 °C span. Field drift of more than 2 °C usually indicates an emissivity mismatch, not a sensor defect. Apply the value with i2cset -y 0 0x5A 0x02 0x3D70 w and verify with i2cget -y 0 0x5A 0x02 w.
10. Troubleshooting Matrix
| Symptom | Likely cause | Remediation |
|---|---|---|
i2cdetect shows -- at 0x5A |
Wiring error, missing pull-up, or I²C not enabled in iot2000setup
|
Re-enable I²C, check 3V3 / GND, add 2.2 kΩ pull-ups |
ELIFECYCLE during galileo-io install |
Native binding cannot compile on Quark x86 | Use mlx-sensor node, or rebuild with Yocto SDK |
| Node-RED crashes on every boot after install | Partial node_modules tree from failed install |
rm -rf node_modules package-lock.json && npm install |
msg.payload.ambient is NaN
|
Sensor returned PEC error or wired reversed | Try i2cdetect -y 0 to confirm presence; swap SDA / SCL |
| Object temperature drifts by 5 °C | Emissivity set to 1.0 default; surface not at 1.0 emissivity | Write 0x02 register with surface-specific ε (e.g. 0x3D70 for human skin) |
| MindSphere returns HTTP 401 | MindConnect agent not onboarded, or token expired | Re-onboard via MindSphere Cockpit; rotate the offline-license file |
| MindSphere returns HTTP 413 | Payload exceeds 1 MB | Reduce publish frequency or trim the envelope |
| Reading stuck at 1027 K | Reading 0x06 (ambient) but interpreting as object, or sensor saturated | Read 0x07 for object temperature; 0x06 is the chip temperature |
| Flow stops emitting after 12 h | libmraa file descriptor leak in galileo-io
|
Switch to mlx-sensor node; add a daily inject → node.restart()
|
| Editor hangs on Deploy | Flow has a node with an invalid configuration (e.g. bad hex address) | Open ~/.node-red/.flows.json and validate the JSON, or deploy one node at a time |
EACCES on /dev/i2c-0
|
Node-RED user not in the i2c group |
usermod -aG i2c node-red and restart the unit |
| Read returns 0xFFFF | PEC mismatch — electrical noise or marginal pull-up | Shorten harness to < 50 mm, increase pull-up strength to 2.2 kΩ |
11. MLX90614 Register Map and Timing Specifications
The MLX90614 register map is documented in the Melexis datasheet for the MLX90614 family. The sub-addresses used by the mlx-sensor node are listed below.
| Sub-address (hex) | Name | Access | Default | Scale | Notes |
|---|---|---|---|---|---|
| 0x02 | Emissivity | R/W | 0xFFFF (ε = 1.0) | 0.0001 per LSB | 16-bit value; emissivity = raw × 0.0001 |
| 0x03 | Config Register 1 | R/W | 0x0074 | — | Bit 7 selects the I²C address LSB |
| 0x04 | Config Register 2 | R/W | 0x0093 | — | Controls FIR / IIR filter |
| 0x05 | Device ID | R | 0x1004 / 0x1007 | — | 0x1004 = MLX90614BAA, 0x1007 = MLX90614BCC |
| 0x06 | Tambient | R | — | 0.02 K per LSB | Chip temperature; convert with (raw × 0.02) − 273.15 |
| 0x07 | Tobject | R | — | 0.02 K per LSB | Object temperature; (raw × 0.02) − 273.15 |
| 0x08 / 0x09 | Raw IR channel 1 / 2 | R | — | — | For diagnostic use |
11.1 I²C timing
| Parameter | Symbol | Min | Typ | Max | Unit |
|---|---|---|---|---|---|
| SCL clock frequency | fSCL | 0 | 100 | 400 | kHz |
| Bus free time between STOP and START | tBUF | 1.3 | — | — | µs |
| Hold time (repeated) START | tHD:STA | 0.6 | — | — | µs |
| LOW period of SCL | tLOW | 1.3 | — | — | µs |
| HIGH period of SCL | tHIGH | 0.6 | — | — | µs |
| Setup time for repeated START | tSU:STA | 0.6 | — | — | µs |
| Data hold time | tHD:DAT | 0 | — | — | ns |
| Data setup time | tSU:DAT | 100 | — | — | ns |
| Rise time SDA / SCL | tR | 20 | — | 300 | ns |
| Fall time SDA / SCL | tF | 20 | — | 300 | ns |
| STOP setup time | tSU:STO | 0.6 | — | — | µs |
| Conversion time (refresh) | tCONV | — | 100 | — | ms |
The MLX90614 refreshes its internal measurement at approximately 10 Hz. Polling faster than this is wasteful; the recommended interval in Section 6 is 100 ms or longer. The IOT2040's internal pull-ups on the IOT2000 example image are too weak (10 kΩ) for reliable 100 kHz communication on bus runs longer than 50 mm; rely on the carrier board's pull-ups and ensure that total bus capacitance stays below 200 pF.
11.2 Power consumption
| Mode | Current (typ) | Current (max) |
|---|---|---|
| Active (continuous measurement) | 1.5 mA | 2.5 mA |
| Sleep (SCL held low > 33 ms) | 2 µA | 10 µA |
| Step / single measurement | 2.5 mA peak | 3.0 mA |
11.3 Verification checklist
-
i2cdetect -y 0returns5aat 0x5A. - Node-RED palette lists the mlx node under the MLX group.
- Debug tab emits one message every 5 s with non-
NaNambient_candobject_c. - Touching the sensor aperture with a finger raises
object_cwithin 2 s. - MindSphere Cockpit shows the time series
MLX90614.ambientandMLX90614.objectupdating in real time. - Service restarts cleanly:
systemctl restart node-red && sleep 10 && systemctl status node-redshows active (running).
12. Frequently Asked Questions
What is the default I²C address of the MLX90614, and can I change it?
The factory default 7-bit address is 0x5A (decimal 90). The MLX90614 also supports 0x5B, 0x5C, and 0x5D by writing the desired LSB to configuration register 1 (sub-address 0x03) and then issuing the dedicated MLX90614 Change I²C Address command. The new address is committed only after a power cycle. In the mlx-sensor node, set Address to the new hex value and redeploy — no physical board modification is required.
Why does Node-RED fail to start after I install node-red-contrib-johnny-five with galileo-io?
The galileo-io package is targeted at the Intel Galileo and Edison boards and bundles a native binding that fails to compile against the Quark x1020's Yocto Linux in the IOT2040 V2.6.1 image. The npm install step ends with an ELIFECYCLE error from node-gyp rebuild, and the partial node_modules tree prevents Node-RED from booting. Run rm -rf node_modules/galileo-io node_modules/johnny-five, restart Node-RED, and use the node-red-contrib-mlx-sensor package instead, which uses the cross-platform i2c-bus library and does not require native compilation.
How do I read the raw register values without Node-RED?
Use i2cget -y 0 0x5A 0x07 w from the IOT2040 shell to read the 16-bit object temperature; convert with (raw & 0x7FFF) × 0.02 − 273.15 to get degrees Celsius. The mlx-sensor node wraps this command and applies the LSB-sign correction automatically. If the read returns 0xFFFF, the PEC byte failed — reduce the I²C clock to 50 kHz or shorten the wiring to bring total capacitance below 100 pF.
Can I run this same setup on a SIMATIC IOT2050 instead of the IOT2040?
Yes, with two adjustments. The IOT2050 (6ES7647-0BA00-0YA2) is an ARM Cortex-A53 (TI AM6528) platform; the I²C bus is exposed as /dev/i2c-1 on the internal header rather than /dev/i2c-0, and the device-tree overlay is toggled through iot2050setup instead of iot2000setup. Update the Bus field in the mlx-sensor node to /dev/i2c-1 and verify with i2cdetect -y 1. The wiring and the MindSphere forwarding steps are identical.
How do I publish the temperature to MindSphere securely?
Use the node-red-contrib-mindconnect node over TLS 1.2 to the MindConnect Element endpoint on port 8443. The agent must be onboarded once via the MindSphere Cockpit by uploading the agent's PEM certificate and assigning the tenant. After onboarding, the node automatically rotates the short-lived bearer token; the IOT2040 system clock must be within 60 s of NTP, which is enforced by the systemd-timesyncd service started at boot on image V2.6.1 and later. Do not disable TLS — MindSphere rejects plain HTTP publishes.