> For the complete documentation index, see [llms.txt](https://wiki.codeandcompile.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.codeandcompile.com/product-reviews/smart-devices/smart-weighing-indicator-controller/ind360-integration-with-mqtt.md).

# IND360 integration with MQTT

In this video and guide, we explore how **IND360** can be seamlessly integrated via MQTT with:

* **Node-RED:** A flow-based, low-code programming tool for wiring together hardware, APIs, and online services, ideal for rapid IIoT prototyping. \
  🔗 <https://nodered.org/docs/getting-started/local>
* **Ignition by Inductive Automation** is an industrial application platform for building SCADA, HMI, and IIoT solutions with unlimited clients and a web-based deployment model. \
  🔗 <https://inductiveautomation.com/downloads/ignition>

***

### 📺 Video Tutorial

{% embed url="<https://youtu.be/7Ml6OcBUadA>" %}

***

### 🔑 What You Will Learn in This Video

In this hands-on demo, you will learn:

* How to connect **IND360 with Node-RED and Ignition SCADA** using **MQTT**
* How to read **weight & status data** in real time
* How to send **commands** such as:
  * Zero scale
  * Tare scale
  * Preset tare
  * Clear tare
* How to log the feedback in the database

***

### Prerequisites

| Requirement           | Details                                      | Status     |
| --------------------- | -------------------------------------------- | ---------- |
| Mettler Toledo IND360 | With network access via Ethernet             | ● Required |
| MQTT Broker           | Shiftr (for testing) or Ignition Distributor | ● Required |
| Node-RED              | Installed locally or on server               | Use Case 1 |
| Ignition Gateway      | Standard edition v8.3.4+                     | Use Case 2 |
| Cirrus Link Modules   | MQTT Distributor + MQTT Engine               | Use Case 2 |

***

### 1.  MQTT Broker Setup

Before connecting the IND360, you need a running MQTT broker. For initial testing, we use **Shiftr,** an MQTT broker with a visual connection map.&#x20;

{% hint style="success" %}
For production use, Ignition's built-in broker is used.
{% endhint %}

{% stepper %}
{% step %}

### Download and install the Shiftr desktop app

Visit `https://www.shiftr.io/desktop` and install the broker locally.
{% endstep %}

{% step %}

### MQTT Broker parameters

Note the MQTT port address
{% endstep %}

{% step %}

### Verify port 1883 is open

Open PowerShell and run the commands below.

```powershell
# Verify Port is Open and Listening
netstat -ano | findstr :1883

# Find Which App Owns the Port
tasklist -ano | findstr <pid>

# Open port 1883 in Windows Firewall
New-NetFirewallRule -DisplayName "MQTT Broker" -Direction Inbound -Protocol TCP -LocalPort 1883 -Action Allow

```

{% hint style="warning" %}
**Important — Firewall**

If the IND360 cannot connect via IP address, the Windows Firewall is blocking port 1883. Run the PowerShell command above to open it.
{% endhint %}
{% endstep %}
{% endstepper %}

### 2.  Enabling MQTT on IND360

In this step, we need to enable the MQTT service on the IND360 device.

{% stepper %}
{% step %}

### Open the IND360 web interface

Navigate to `https://192.168.0.8` (replace with your IND360 IP) and login using `002`.
{% endstep %}

{% step %}

### Navigate to MQTT settings

Go to **Communication → Service** and find the MQTT section.
{% endstep %}

{% step %}

### Configure MQTT parameters

Fill in all fields as shown in the table below.

| Parameter   | Value          | Notes                             |
| ----------- | -------------- | --------------------------------- |
| Client ID   | `IND360`       | Unique identifier for this device |
| Host        | `192.168.0.24` | IP of your broker machine         |
| Port        | `1883`         | Standard MQTT port                |
| Topic       |                | Root topic prefix                 |
| Username    |                | Must match broker user            |
| Supervision | `Enabled`      | Enables keep-alive monitoring     |

{% hint style="info" %}
**Topic Structure**

The IND360 automatically appends its serial number and sub-paths to the root topic. For example: `ind360/devices/C249346600/scales/_/measurements`
{% endhint %}
{% endstep %}

{% step %}

### Click SET, then CONNECT

Status should change to **Connected**.
{% endstep %}
{% endstepper %}

***

### 3.  Testing with Shiftr

Use Shiftr's visual broker map to confirm the IND360 is publishing data before integrating with Node-RED or Ignition.

**Open the Shiftr dashboard to visualize the data flowing from the IND360 device**

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLd2M9UNfMTnw9DjDdZJz%2Fuploads%2FvQCsuBulKx3R5NjHO0a4%2Fmqtt%20broker.mp4?alt=media&token=9f4b62f7-7759-4068-80fc-9d42dbfa457e>" %}

***

### Use Case 1: Node-RED Integration

Use Node-RED as a lightweight MQTT client to read live weight data and send commands to the IND360 via the Shiftr broker.

#### **Reading Weight Data**

Subscribe to the measurements topic to receive live weight data as a JSON array.

* ind360/devices/C249346600
* ind360/devices/C249346600/alarms
* ind360/devices/C249346600/scales/\_
* ind360/devices/C249346600/scales/\_/measurements

<figure><img src="/files/lamc447UeZ93zIdGpOG5" alt=""><figcaption></figcaption></figure>

```javascript
//function 21
msg.payload = msg.payload[0].gross + ' ' + msg.payload[0].uomCode;
return msg;

//function 22
msg.payload = msg.payload[0].net;
return msg;

//function 23
msg.payload = msg.payload[0].tare + ' ' + msg.payload[0].uomCode;
return msg;

```

#### Sending Commands <a href="#nodered-write" id="nodered-write"></a>

Each command follows the same pattern — an Inject node triggers a Function node that builds the payload, which is then published via an MQTT Out node.

<table><thead><tr><th width="116.3333740234375">Command</th><th width="153.333251953125">Description</th><th>Topic</th></tr></thead><tbody><tr><td>tare</td><td>Sets net weight to zero using current gross as tare offset</td><td><p><strong>Invoke:</strong></p><p><code>ind360/devices/C249346600/commands/tare/invoke</code></p><p><strong>Result:</strong><br><code>ind360/devices/C249346600/commands/tare/result</code></p></td></tr><tr><td>tare-clear</td><td>Removes the tare offset, gross equals net again</td><td><p><strong>Invoke:</strong></p><p><code>ind360/devices/C249346600/commands/tare-clear/invoke</code></p><p><strong>Result:</strong><br><code>ind360/devices/C249346600/commands/tare-clear/result</code></p></td></tr><tr><td>zero</td><td>Sets current gross weight as the new zero reference point</td><td><p><strong>Invoke:</strong></p><p><code>ind360/devices/C249346600/commands/zero/invoke</code></p><p><strong>Result:</strong><br><code>ind360/devices/C249346600/commands/zero/result</code></p></td></tr><tr><td>tare-preset</td><td>Applies a manually entered known tare value</td><td><p><strong>Invoke:</strong></p><p><code>ind360/devices/C249346600/commands/tare-preset/invoke</code></p><p><strong>Result:</strong><br><code>ind360/devices/C249346600/commands/tare-preset/result</code></p></td></tr></tbody></table>

<figure><img src="/files/TvC2xhAQQdBuYjKc4v9c" alt=""><figcaption></figcaption></figure>

**Tare Command — Function Node**

```javascript
msg.payload = {
    "$correlation": "TareTest1",   // Unique ID to track this request
    "$ttl": 42,                   // Time-to-live in seconds
    "responseTopic": null,         // Set null if no response topic needed
    "command": "tare",             // Command to execute
    "content": null               // Not required for tare
}
return msg;
```

**Clear Command — Function Node**

```javascript
msg.payload =
{
    "$correlation": "TareTest1",
    "$ttl": 42,
    "responseTopic": null,
    "command": "tare-clear",
    "content" : null
}
return msg;
```

**Zero Command — Function Node**

```javascript
msg.payload =
{
    "$correlation": "Zero-Test1",
    "$ttl": 42,
    "responseTopic": null,
    "command": "zero",
    "content" : null
}
return msg;
```

**Preset Tare Command — Function Node**

```javascript
msg.payload = {
    "$correlation": "Tare-PresetTest1",
    "$ttl": 42,
    "responseTopic": null,
    "command": "tare-preset",
    "content": {
        "value": "2.0",             // Preset tare value
        "unit": "Kg"               // Unit — must match scale config
    }
}
return msg;
```

***

### Dashboard

You can use the dashboard 2.0 to visualize your value as shown below. Feel free to use the NodeRed flow for a ready-to-use dashboard.&#x20;

<div align="left"><figure><img src="/files/MKBAbyVhPR2wwsV94z1L" alt="" width="314"><figcaption></figcaption></figure></div>

{% file src="/files/17hlvKsxADLURl6EfcRg" %}

***

### Use Case 2: Ignition Integration

Ignition acts as both the MQTT broker (via MQTT Distributor) and the subscriber (via MQTT Engine). No external broker needed.&#x20;

{% stepper %}
{% step %}

### Install Ignition

Sign up for an account, download, and install Ignition. <https://inductiveautomation.com/>
{% endstep %}

{% step %}

### Install MQTT Modules

Visit `https://inductiveautomation.com/downloads/third-party-modules/8.3.4` — Match the version to your Ignition installation.

**Install via Gateway → Platform → Modules**

<table><thead><tr><th width="175.33331298828125">Module</th><th width="437.0001220703125">Role</th><th>Port</th></tr></thead><tbody><tr><td>MQTT Distributor</td><td>Acts as the MQTT broker inside Ignition</td><td>1883</td></tr><tr><td>MQTT Engine</td><td>Subscribes and converts MQTT data to Ignition tags</td><td>—</td></tr><tr><td></td><td></td><td></td></tr></tbody></table>
{% endstep %}

{% step %}

### Configure MQTT Distributor

**Gateway → Connections → MQTT Distributor → Settings**

Enable the distributor and verify that the port is set to `1883`.

**Create broker users**

Go to Users and create two accounts:

| Username      | Password   | Used by                                              |
| ------------- | ---------- | ---------------------------------------------------- |
| `ind360`      | `ind360`   | IND360 device                                        |
| `ignition`    | `ignition` | MQTT Engine internal connection                      |
| `nodered`     | `nodered`  | (Optional) For Node-RED to subscribe and publishdata |
| {% endstep %} |            |                                                      |

{% step %}

### Configure MQTT Engine

**Gateway → Connections → MQTT Engine → Servers → Add Server**

| Field         | Value                     |
| ------------- | ------------------------- |
| URL           | `tcp://192.168.0.24:1883` |
| Server Set    | `Default Set`             |
| Username      | `ignition`                |
| Password      | `ignition`                |
| {% endstep %} |                           |

{% step %}

### Create Custom Namespace

{% hint style="info" %}
**Why a Custom Namespace?**

MQTT Engine's default namespaces (Sparkplug B and Elecsys) expect specific structured payload formats. The IND360 publishes plain JSON — so we must create a custom namespace that tells the Engine how to parse and tag the incoming data.
{% endhint %}

**Gateway → MQTT Engine → Namespaces → Custom → Create New**

| Field                | Value           | Notes                            |
| -------------------- | --------------- | -------------------------------- |
| Namespace Name       | `IND360`        | Unique identifier                |
| MQTT Topic           | `#`             | Wildcard captures all sub-topics |
| Root Folder          | `IND360_folder` | Tag browser folder name          |
| JSON Payload         | ✅ Checked       | Auto-parses JSON into tags       |
| Create Writable Tags | ✅ Checked       | Enables writing back to device   |
| {% endstep %}        |                 |                                  |

{% step %}

### &#x20;Dashboard & Commands

Once tags appear in the Tag Browser under MQTT Engine, bind them to Vision components for live display.

<div align="center"><figure><img src="/files/XTkJUsvwx9CZr2j1Z06t" alt=""><figcaption></figcaption></figure></div>

#### Useful scripts

Below are the scripts that are used for publishing the data to the MQTT broker via buttons

**Tare Button Script**

```python
import json

# Build the tare command payload
payload = json.dumps({
    "$correlation": "IgnitionTare1",   # Unique ID to track this request
    "$ttl": 42,                        # Time-to-live in seconds
    "responseTopic": None,             # No response topic needed
    "command": "tare",                 # Command to execute on IND360
    "content": None                   # Not required for tare
})

# Publish to IND360 via Ignition MQTT Engine
system.cirruslink.engine.publish(
    "Ignition Distributor",                                     # Server name in MQTT Engine → Servers
    "ind360/devices/C249346600/commands/tare/invoke",           # Command topic
    payload.encode(),                                           # Must be bytes — .encode() converts string
    1,                                                          # QoS 1 = at least once delivery
    False                                                       # Retain = False
)
```

{% hint style="warning" %}
The payload must be byte-encoded using `.encode()`. Passing a plain string causes a silent failure; no error is shown, but the command is never published. Learn more about Python scripting here: <https://docs.chariot.io/display/CLD80/ME%3A+Python+Scripting>
{% endhint %}

**Clear Button Script**

```python
import json

# Build the tare-clear command payload
# Tare-clear resets the tare back to zero (removes the tare offset)
payload = json.dumps({
    "$correlation": "IgnitionClear1",  # Unique ID to track this request
    "$ttl": 42,                        # Time-to-live in seconds before message expires
    "responseTopic": None,             # Set to None if no response topic is needed
    "command": "tare-clear",           # The command to execute — clears the existing tare value
    "content": None                    # Additional content — not required for tare-clear command
})

# Publish the clear command to the MQTT broker via Ignition MQTT Engine
system.cirruslink.engine.publish(
    "Ignition Distributor",                                        # MQTT server name as configured in MQTT Engine > Servers
    "ind360/devices/C249346600/commands/tare-clear/invoke",        # MQTT topic — tare-clear command path for device C249346600
    payload.encode(),                                              # Payload must be bytes — .encode() converts string to bytes
    1,        
```

**Zero Button Script**

```python
import json

# Build the zero command payload as a Python dictionary
# Zero command sets the current scale reading as the new zero reference point
payload = json.dumps({
    "$correlation": "IgnitionZero1",   # Unique ID to track this request (can be any string)
    "$ttl": 42,                        # Time-to-live in seconds before message expires
    "responseTopic": None,             # Set to None if no response topic is needed
    "command": "zero",                 # The command to execute on IND360 (zero = set current weight as new zero)
    "content": None                    # Additional content — not required for zero command
})

# Publish the zero command to the MQTT broker via Ignition MQTT Engine
system.cirruslink.engine.publish(
    "Ignition Distributor",                                    # MQTT server name as configured in MQTT Engine > Servers
    "ind360/devices/C249346600/commands/zero/invoke",          # MQTT topic — device serial number C249346600 is part of the path
    payload.encode(),                                          # Payload must be bytes — .encode() converts string to bytes
    1,                                                         # QoS level 1 = at least once delivery (reliable)
    False                                                      # Retain flag — False means broker won't store this message
)
```

**Preset Tare Button Script**

* Take an input field and rename that to 'presetTare'
* Add the script below to the Preset Tare button.

```python
import json

# Read the preset value from the input field on the dashboard
preset_value = event.source.parent.getComponent("presetTare").value

if preset_value <= 0:
    system.gui.messageBox("Please enter a valid preset tare value", "Invalid Input")
else:
    payload = json.dumps({
        "$correlation": "IgnitionPresetTare1",
        "$ttl": 42,
        "responseTopic": None,
        "command": "tare-preset",
        "content": {
            "value": str(preset_value),     # Dynamic value from input field
            "unit": "Kg"
        }
    })
    system.cirruslink.engine.publish(
        "Ignition Distributor",
        "ind360/devices/C249346600/commands/tare-preset/invoke",
        payload.encode(), 1, False
    )
```

{% endstep %}

{% step %}

### Feedback Logging to Database

Log real device confirmation to SQLite using a Gateway Tag Change script triggered by the result timestamp.

**Create Database Table**

First, create a table in the default database. Run the following script in the Tools > Database Query browser.

```sql
CREATE TABLE IF NOT EXISTS ind360_command_log (
        id          INT AUTO_INCREMENT PRIMARY KEY,
        command     TEXT,
        result      TEXT,
        timestamp   TEXT
    )
```

To delete the table, use the following script

```sql
Drop table <table name>
```

**Gateway Tag Change Script:**

**tare\_result**

Watch tag: `[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare/result/_timestamp`

```python
def onTagChange(initialChange, newValue, previousValue, event, executionCount):
	
    # Get raw IND360 timestamp e.g. "2026-04-28T11:45:41.000Z"
    raw_timestamp = str(newValue.value)

    # Convert ISO 8601 format to SQLite compatible format
    # Replace the T separator with a space and remove the Z suffix
    clean_timestamp = raw_timestamp.replace("T", " ").replace("Z", "")
    # Result: "2026-04-28 11:45:41.000" ✅

    # Read result fields from MQTT Engine tags
    description = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare/result/content/description"
    ])[0].value

    code = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare/result/code"
    ])[0].value

    correlation = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare/result/_correlation"
    ])[0].value

    # Log to database with cleaned SQLite compatible timestamp
    system.db.runPrepUpdate(
        "INSERT INTO ind360_command_log (command, result, timestamp) VALUES (?, ?, ?)",
        ["tare", description, clean_timestamp],
        database="Sample_SQLite_Database"
    )
```

**clear\_result**

Watch tag: `[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/clear/result/_timestamp`

```python
def onTagChange(initialChange, newValue, previousValue, event, executionCount):
	
    # Get raw IND360 timestamp e.g. "2026-04-28T11:45:41.000Z"
    raw_timestamp = str(newValue.value)

    # Convert ISO 8601 format to SQLite compatible format
    # Replace the T separator with a space and remove the Z suffix
    clean_timestamp = raw_timestamp.replace("T", " ").replace("Z", "")
    # Result: "2026-04-28 11:45:41.000" 

    # Read result fields from MQTT Engine tags
    description = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-clear/result/content/description"
    ])[0].value

    code = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-clear/result/code"
    ])[0].value

    correlation = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-clear/result/_correlation"
    ])[0].value

    # Log to database with cleaned SQLite compatible timestamp
    system.db.runPrepUpdate(
        "INSERT INTO ind360_command_log (command, result, timestamp) VALUES (?, ?, ?)",
        ["clear", description, clean_timestamp],
        database="Sample_SQLite_Database"
    )
```

**zero\_result**

Watch tag: `[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/zero/result/_timestamp`

```python
def onTagChange(initialChange, newValue, previousValue, event, executionCount):
	
    # Get raw IND360 timestamp e.g. "2026-04-28T11:45:41.000Z"
    raw_timestamp = str(newValue.value)

    # Convert ISO 8601 format to SQLite compatible format
    # Replace the T separator with a space and remove the Z suffix
    clean_timestamp = raw_timestamp.replace("T", " ").replace("Z", "")
    # Result: "2026-04-28 11:45:41.000" ✅

    # Read result fields from MQTT Engine tags
    description = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/zero/result/content/description"
    ])[0].value

    code = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/zero/result/code"
    ])[0].value

    correlation = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/zero/result/_correlation"
    ])[0].value

    # Log to database with cleaned SQLite compatible timestamp
    system.db.runPrepUpdate(
        "INSERT INTO ind360_command_log (command, result, timestamp) VALUES (?, ?, ?)",
        ["zero", description, clean_timestamp],
        database="Sample_SQLite_Database"
    )
```

**preset\_tare**

Watch tag: `[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-preset/result/_timestamp`

```python
def onTagChange(initialChange, newValue, previousValue, event, executionCount):
	
    # Get raw IND360 timestamp e.g. "2026-04-28T11:45:41.000Z"
    raw_timestamp = str(newValue.value)

    # Convert ISO 8601 format to SQLite compatible format
    # Replace the T separator with a space and remove the Z suffix
    clean_timestamp = raw_timestamp.replace("T", " ").replace("Z", "")
    # Result: "2026-04-28 11:45:41.000" ✅

    # Read result fields from MQTT Engine tags
    description = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-preset/result/content/description"
    ])[0].value

    code = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-preset/result/code"
    ])[0].value

    correlation = system.tag.readBlocking([
        "[MQTT Engine]IND360_folder/ind360/devices/C249346600/commands/tare-preset/result/_correlation"
    ])[0].value

    # Log to database with cleaned SQLite compatible timestamp
    system.db.runPrepUpdate(
        "INSERT INTO ind360_command_log (command, result, timestamp) VALUES (?, ?, ?)",
        ["tare", description, clean_timestamp],
        database="Sample_SQLite_Database"
    )
```

**Table SQL Query Binding**

To visualize the feedback in the table. Add a table to your screen and bind the data with the following script

```sql
SELECT
    command   AS Command,
    result    AS Result,
    timestamp AS Timestamp
FROM ind360_command_log
ORDER BY datetime(timestamp) DESC
LIMIT 50
```

**Clear button**

To clear the values in the table, you can use the following script

```python
# Show confirmation dialog before clearing
# Prevents accidental deletion of command history
result = system.gui.confirm(
    "Are you sure you want to clear all feedback records?",
    "Clear Feedback Table"
)

# Only delete if operator clicked Yes
if result:
    system.db.runUpdateQuery(
        "DELETE FROM ind360_command_log",
        database="Sample_SQLite_Database"
    )
```

{% endstep %}
{% endstepper %}

***

### Topic Structure Reference

```python
ind360/
├── devices/C249346600/
│ ├── scales/_/measurements #subscribe
│ ├── scales/_ #subscribe
│ ├── alarms #subscribe
│ └── commands/
│ ├── tare/invoke #publish
│ ├── tare/result #subscribe
│ ├── tare-clear/invoke #publish
│ ├── tare-clear/result #subscribe
│ ├── zero/invoke #publish
│ ├── zero/result #subscribe
│ ├── tare-preset/invoke #publish
│ └── tare-preset/result #subscribe
```

### Command Payload Reference

<table><thead><tr><th width="150.3333740234375">Command</th><th width="351.333251953125">content field</th><th>Notes</th></tr></thead><tbody><tr><td><code>tare</code></td><td><code>null</code></td><td>No content needed</td></tr><tr><td><code>tare-clear</code></td><td><code>null</code></td><td>No content needed</td></tr><tr><td><code>zero</code></td><td><code>null</code></td><td>No content needed</td></tr><tr><td><code>tare-preset</code></td><td><code>{"value": "2.0", "unit": "Kg"}</code></td><td>Value and unit required</td></tr></tbody></table>

#### Ignition Project

You can import this project in your Ignition designer to use it as a prototype:

{% file src="/files/87quKNpKqKT9Bz35QqCo" %}

***

## Key notes:

{% embed url="<https://www.canva.com/design/DAG7MP4dokE/MQMZPZt4YaA6um4lrJCE5A/view?utlId=hebd7857a29&utm_campaign=designshare&utm_content=DAG7MP4dokE&utm_medium=link2&utm_source=uniquelinks>" %}

***

## ♥️ Work With Me

I regularly test **industrial automation and IIoT devices**. If you’d like me to **review your product** or showcase it in my courses and YouTube channel:

📧 Email: <rajvir@codeandcompile.com> or drop me a message on [LinkedIn](https://www.linkedin.com/in/singhrajvir/)
