node-red-contrib-modbus-modpackqt 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -17
- package/nodes/modpackqt-config.html +90 -10
- package/nodes/modpackqt-slave-read.html +79 -12
- package/nodes/modpackqt-slave-read.js +41 -13
- package/nodes/modpackqt-slave-write.html +85 -12
- package/nodes/modpackqt-slave-write.js +36 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,11 +45,28 @@ npm install node-red-contrib-modbus-modpackqt
|
|
|
45
45
|
|
|
46
46
|
## Prerequisites
|
|
47
47
|
|
|
48
|
-
1. **
|
|
48
|
+
1. **Sign up at [modpackqt.com](https://modpackqt.com)**
|
|
49
|
+
A free account works for all master nodes. Slave nodes require a **paid plan**.
|
|
50
|
+
|
|
51
|
+
2. **Download and start the ModPackQT Gateway app**
|
|
49
52
|
Download it from [modpackqt.com/download](https://modpackqt.com/download) and run it.
|
|
50
53
|
By default it listens on **port 8502** (`localhost:8502`).
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
3. **Create an API key**
|
|
56
|
+
In the ModPackQT web app go to **Settings → API Keys → New Key**. Paste it into the `modpackqt-config` node.
|
|
57
|
+
|
|
58
|
+
4. **For slave nodes** — create and start at least one slave in the ModPackQT app first (Slave Simulator → New Slave). Note the **Slave ID** shown — you'll need it in Node-RED.
|
|
59
|
+
|
|
60
|
+
> **Plan limits**
|
|
61
|
+
>
|
|
62
|
+
> | Feature | Free | Paid |
|
|
63
|
+
> |---|---|---|
|
|
64
|
+
> | Master Read (`modpackqt-master-read`) | ✓ | ✓ |
|
|
65
|
+
> | Master Write (`modpackqt-master-write`) | ✓ | ✓ |
|
|
66
|
+
> | Slave Write (`modpackqt-slave-write`) | ✗ | ✓ |
|
|
67
|
+
> | Slave Read (`modpackqt-slave-read`) | ✗ | ✓ |
|
|
68
|
+
>
|
|
69
|
+
> [View plans & upgrade →](https://modpackqt.com/#pricing)
|
|
53
70
|
|
|
54
71
|
---
|
|
55
72
|
|
|
@@ -60,8 +77,8 @@ npm install node-red-contrib-modbus-modpackqt
|
|
|
60
77
|
| `modpackqt-config` | Config | Shared gateway connection (host, port, API key) |
|
|
61
78
|
| `modpackqt-master-read` | Modbus Master | Read Modbus TCP registers — FC1/FC2/FC3/FC4 |
|
|
62
79
|
| `modpackqt-master-write` | Modbus Master | Write Modbus TCP registers — FC5/FC6/FC15/FC16 |
|
|
63
|
-
| `modpackqt-slave-read` | Modbus Slave | Read registers from a ModPackQT
|
|
64
|
-
| `modpackqt-slave-write` | Modbus Slave |
|
|
80
|
+
| `modpackqt-slave-read` | Modbus Slave *(paid)* | Read registers from a ModPackQT slave |
|
|
81
|
+
| `modpackqt-slave-write` | Modbus Slave *(paid)* | Push values into a ModPackQT slave (build a live Modbus device from Node-RED) |
|
|
65
82
|
|
|
66
83
|
---
|
|
67
84
|
|
|
@@ -114,21 +131,45 @@ The `modpackqt-master-write` node writes registers to **any Modbus TCP slave dev
|
|
|
114
131
|
|
|
115
132
|
---
|
|
116
133
|
|
|
117
|
-
##
|
|
134
|
+
## Building a Modbus Slave from Node-RED *(Paid plan)*
|
|
118
135
|
|
|
119
|
-
|
|
136
|
+
Use `modpackqt-slave-write` to make Node-RED the **data source** for a live Modbus slave device. Any external Modbus master (PLC, SCADA, HMI) that connects to the ModPackQT slave port will read whatever values you last pushed from Node-RED.
|
|
137
|
+
|
|
138
|
+
**Typical flow:**
|
|
139
|
+
```
|
|
140
|
+
[MQTT / Sensor / Timer] → [Function: scale to integer] → [modpackqt-slave-write addr=0] → [External Modbus master reads]
|
|
141
|
+
```
|
|
120
142
|
|
|
121
|
-
**
|
|
143
|
+
**Use cases:**
|
|
144
|
+
- Publish MQTT sensor data as Modbus TCP registers
|
|
145
|
+
- Simulate a PLC register map for HMI/SCADA testing
|
|
146
|
+
- Bridge OPC-UA, REST, or database data to Modbus
|
|
147
|
+
|
|
148
|
+
**Slave write input — all of these work:**
|
|
149
|
+
```js
|
|
150
|
+
msg.payload = 234; // single integer
|
|
151
|
+
msg.payload = [234, 1013, 65]; // array — writes 3 registers starting at address 0
|
|
152
|
+
msg.payload = "[234, 1013, 65]"; // JSON string also works (inject string type)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Override at runtime:**
|
|
156
|
+
```js
|
|
157
|
+
msg.slaveId = "42"; // select a different slave per message
|
|
158
|
+
msg.address = 10; // start at register 10 instead of the configured address
|
|
159
|
+
msg.registerType = "coil"; // override register type
|
|
160
|
+
```
|
|
122
161
|
|
|
123
162
|
**Slave read output:**
|
|
124
163
|
```json
|
|
125
164
|
{
|
|
126
165
|
"payload": {
|
|
127
|
-
"values": [
|
|
166
|
+
"values": [234, 1013, 65],
|
|
128
167
|
"registerType": "holding",
|
|
129
|
-
"address": 0
|
|
168
|
+
"address": 0,
|
|
169
|
+
"quantity": 3,
|
|
170
|
+
"slaveId": "42"
|
|
130
171
|
},
|
|
131
|
-
"topic": "slave/
|
|
172
|
+
"topic": "slave/42/holding/0"
|
|
132
173
|
}
|
|
133
174
|
```
|
|
134
175
|
|
|
@@ -136,7 +177,7 @@ The `modpackqt-slave-read` and `modpackqt-slave-write` nodes interact with ModPa
|
|
|
136
177
|
|
|
137
178
|
## Example Flows
|
|
138
179
|
|
|
139
|
-
### Poll Holding Registers Every 5 Seconds (
|
|
180
|
+
### Poll Holding Registers Every 5 Seconds (Modbus Master)
|
|
140
181
|
|
|
141
182
|
```
|
|
142
183
|
[Inject (repeat 5s)] → [modpackqt-master-read FC3 target=192.168.1.10:502 unitId=1 addr=0 qty=10] → [Debug]
|
|
@@ -148,13 +189,14 @@ The `modpackqt-slave-read` and `modpackqt-slave-write` nodes interact with ModPa
|
|
|
148
189
|
[Inject (payload=1234)] → [modpackqt-master-write FC6 target=192.168.1.10:502 unitId=1 addr=100] → [Debug]
|
|
149
190
|
```
|
|
150
191
|
|
|
151
|
-
###
|
|
192
|
+
### Build a Slave — Publish Sensor Data as Modbus (Paid plan)
|
|
152
193
|
|
|
153
194
|
```
|
|
154
|
-
[
|
|
155
|
-
[modpackqt-slave-read Holding addr=0 qty=3] → [Debug]
|
|
195
|
+
[MQTT in] → [Function: msg.payload = parseInt(msg.payload * 10)] → [modpackqt-slave-write Holding addr=0] → [Debug]
|
|
156
196
|
```
|
|
157
197
|
|
|
198
|
+
Any Modbus master on the network now reads live sensor values from the slave.
|
|
199
|
+
|
|
158
200
|
### Import the Demo Flow
|
|
159
201
|
|
|
160
202
|
1. Open Node-RED → **Menu → Import**
|
|
@@ -191,9 +233,11 @@ Add one `modpackqt-config` node per gateway instance. All other nodes reference
|
|
|
191
233
|
|
|
192
234
|
| Issue | Solution |
|
|
193
235
|
|---|---|
|
|
194
|
-
| `connect ECONNREFUSED localhost:8502` |
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
236
|
+
| `connect ECONNREFUSED localhost:8502` | The ModPackQT Gateway app is not running. Start it first. |
|
|
237
|
+
| `401 Unauthorized` | Missing or invalid API key — check `modpackqt-config`. |
|
|
238
|
+
| `403 Forbidden` on slave nodes | Your plan does not include the Slave Simulator. [Upgrade your plan →](https://modpackqt.com/#pricing) |
|
|
239
|
+
| `slave not found — check Slave ID` | The Slave ID is wrong or that slave was deleted. Check in ModPackQT → Slave Simulator. |
|
|
240
|
+
| `gateway not running` on slave nodes | The Gateway app is stopped. Open ModPackQT and click Start Gateway. |
|
|
197
241
|
| `Cannot find module 'axios'` | Run `npm install` in `~/.node-red`. |
|
|
198
242
|
| Nodes not appearing after install | Restart Node-RED completely after `npm install`. |
|
|
199
243
|
| Node-RED version error | Requires Node-RED ≥ 2.0.0 and Node.js ≥ 14. |
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
RED.nodes.registerType('modpackqt-config', {
|
|
3
3
|
category: 'config',
|
|
4
4
|
defaults: {
|
|
5
|
-
name: { value: 'ModPackQT', required: false },
|
|
5
|
+
name: { value: 'ModPackQT Gateway', required: false },
|
|
6
6
|
host: { value: 'localhost', required: true },
|
|
7
|
-
port: { value:
|
|
7
|
+
port: { value: 8502, required: true, validate: RED.validators.number() }
|
|
8
8
|
},
|
|
9
9
|
credentials: {
|
|
10
10
|
apiKey: { type: 'password' }
|
|
11
11
|
},
|
|
12
12
|
label: function() {
|
|
13
|
-
return this.name ||
|
|
13
|
+
return this.name || (this.host + ':' + this.port);
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
16
|
</script>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<script type="text/html" data-template-name="modpackqt-config">
|
|
19
19
|
<div class="form-row">
|
|
20
20
|
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
21
|
-
<input type="text" id="node-config-input-name" placeholder="ModPackQT">
|
|
21
|
+
<input type="text" id="node-config-input-name" placeholder="ModPackQT Gateway">
|
|
22
22
|
</div>
|
|
23
23
|
<div class="form-row">
|
|
24
24
|
<label for="node-config-input-host"><i class="fa fa-globe"></i> Host</label>
|
|
@@ -26,23 +26,103 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
<div class="form-row">
|
|
28
28
|
<label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
|
|
29
|
-
<input type="number" id="node-config-input-port" placeholder="
|
|
29
|
+
<input type="number" id="node-config-input-port" placeholder="8502" min="1" max="65535">
|
|
30
30
|
</div>
|
|
31
31
|
<div class="form-row">
|
|
32
32
|
<label for="node-config-input-apiKey"><i class="fa fa-key"></i> API Key</label>
|
|
33
|
-
<input type="password" id="node-config-input-apiKey" placeholder="
|
|
33
|
+
<input type="password" id="node-config-input-apiKey" placeholder="Paste your API key from ModPackQT → Settings">
|
|
34
|
+
</div>
|
|
35
|
+
<div class="form-tips">
|
|
36
|
+
<b>Default setup:</b> Host <code>localhost</code>, Port <code>8502</code>.<br>
|
|
37
|
+
Get your API key from <b>ModPackQT app → Settings → API Keys</b>.
|
|
34
38
|
</div>
|
|
35
39
|
</script>
|
|
36
40
|
|
|
37
41
|
<script type="text/html" data-help-name="modpackqt-config">
|
|
38
|
-
<p>
|
|
42
|
+
<p>
|
|
43
|
+
Shared connection settings for all ModPackQT nodes. Defines which
|
|
44
|
+
<b>ModPackQT Gateway</b> instance to communicate with and how to authenticate.
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<h3>What is the ModPackQT Gateway?</h3>
|
|
48
|
+
<p>
|
|
49
|
+
The Gateway is a small app that runs on your local machine (or a server on your network).
|
|
50
|
+
It acts as a bridge between Node-RED and your Modbus devices, and also hosts the
|
|
51
|
+
Slave Simulator. All ModPackQT nodes talk to this Gateway's REST API.
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<h3>First-time setup</h3>
|
|
55
|
+
<ol>
|
|
56
|
+
<li>
|
|
57
|
+
<b>Sign up</b> at <a href="https://modpackqt.com" target="_blank">modpackqt.com</a>
|
|
58
|
+
— a free account lets you use the master read/write nodes.
|
|
59
|
+
Slave and advanced features require a paid plan.
|
|
60
|
+
</li>
|
|
61
|
+
<li>
|
|
62
|
+
<b>Download the Gateway app</b> — log in to ModPackQT, open
|
|
63
|
+
<em>Settings → Gateway</em> and download the installer for your OS (Windows/Mac/Linux).
|
|
64
|
+
</li>
|
|
65
|
+
<li>
|
|
66
|
+
<b>Run the Gateway</b> — it starts a local REST API on port <code>8502</code> by default.
|
|
67
|
+
You will see a <em>Running</em> status in the app when it is ready.
|
|
68
|
+
</li>
|
|
69
|
+
<li>
|
|
70
|
+
<b>Create an API Key</b> — in the ModPackQT web app, go to
|
|
71
|
+
<em>Settings → API Keys → New Key</em>. Copy the key and paste it into the
|
|
72
|
+
<em>API Key</em> field in this config node.
|
|
73
|
+
</li>
|
|
74
|
+
<li>
|
|
75
|
+
<b>Set Host and Port</b>:
|
|
76
|
+
<ul>
|
|
77
|
+
<li>If Node-RED runs on the same machine as the Gateway: <code>localhost</code> / <code>8502</code></li>
|
|
78
|
+
<li>If the Gateway is on another machine: use that machine's IP address</li>
|
|
79
|
+
</ul>
|
|
80
|
+
</li>
|
|
81
|
+
</ol>
|
|
82
|
+
|
|
39
83
|
<h3>Properties</h3>
|
|
40
84
|
<dl class="message-properties">
|
|
41
85
|
<dt>Host <span class="property-type">string</span></dt>
|
|
42
|
-
<dd>
|
|
86
|
+
<dd>
|
|
87
|
+
Hostname or IP address of the machine running the ModPackQT Gateway.
|
|
88
|
+
Default: <code>localhost</code>.
|
|
89
|
+
</dd>
|
|
43
90
|
<dt>Port <span class="property-type">number</span></dt>
|
|
44
|
-
<dd>
|
|
91
|
+
<dd>
|
|
92
|
+
TCP port the Gateway REST API listens on. Default: <code>8502</code>.
|
|
93
|
+
Change this only if you configured a custom port in the Gateway settings.
|
|
94
|
+
</dd>
|
|
45
95
|
<dt>API Key <span class="property-type">password</span></dt>
|
|
46
|
-
<dd>
|
|
96
|
+
<dd>
|
|
97
|
+
Your ModPackQT API key. Required for authenticated operations.
|
|
98
|
+
Get it from <em>ModPackQT → Settings → API Keys</em>.
|
|
99
|
+
</dd>
|
|
47
100
|
</dl>
|
|
101
|
+
|
|
102
|
+
<h3>Plan limits</h3>
|
|
103
|
+
<table>
|
|
104
|
+
<thead><tr><th>Feature</th><th>Free</th><th>Paid</th></tr></thead>
|
|
105
|
+
<tbody>
|
|
106
|
+
<tr><td>Master Read (modpackqt-master-read)</td><td>✓</td><td>✓</td></tr>
|
|
107
|
+
<tr><td>Master Write (modpackqt-master-write)</td><td>✓</td><td>✓</td></tr>
|
|
108
|
+
<tr><td>Slave Write (modpackqt-slave-write)</td><td>✗</td><td>✓</td></tr>
|
|
109
|
+
<tr><td>Slave Read (modpackqt-slave-read)</td><td>✗</td><td>✓</td></tr>
|
|
110
|
+
<tr><td>Number of Slaves</td><td>0</td><td>Plan dependent</td></tr>
|
|
111
|
+
</tbody>
|
|
112
|
+
</table>
|
|
113
|
+
<p>
|
|
114
|
+
<a href="https://modpackqt.com/#pricing" target="_blank">View plans & upgrade →</a>
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<h3>Troubleshooting</h3>
|
|
118
|
+
<ul>
|
|
119
|
+
<li><b>ECONNREFUSED</b> — the Gateway app is not running. Start it from the ModPackQT app.</li>
|
|
120
|
+
<li><b>401 Unauthorized</b> — missing or invalid API key.</li>
|
|
121
|
+
<li><b>403 Forbidden</b> — your plan does not include this feature, or you have reached a limit.</li>
|
|
122
|
+
<li><b>404 Not Found</b> — the Slave ID or Connection Profile ID does not exist in your account.</li>
|
|
123
|
+
</ul>
|
|
124
|
+
|
|
125
|
+
<p>
|
|
126
|
+
<a href="https://modpackqt.com/node-red" target="_blank">Full Node-RED documentation →</a>
|
|
127
|
+
</p>
|
|
48
128
|
</script>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: '' },
|
|
7
7
|
server: { value: '', type: 'modpackqt-config', required: true },
|
|
8
|
-
slaveId: { value: ''
|
|
8
|
+
slaveId: { value: '' },
|
|
9
9
|
registerType: { value: 'holding', required: true },
|
|
10
10
|
address: { value: 0, required: true, validate: RED.validators.number() },
|
|
11
11
|
quantity: { value: 1, required: true, validate: RED.validators.number() },
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
outputs: 1,
|
|
16
16
|
icon: 'font-awesome/fa-server',
|
|
17
17
|
label: function() {
|
|
18
|
-
return this.name ||
|
|
18
|
+
return this.name || ('Slave Read ' + (this.registerType || 'holding') + ' @' + this.address);
|
|
19
19
|
},
|
|
20
20
|
paletteLabel: 'slave read'
|
|
21
21
|
});
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<div class="form-row">
|
|
34
34
|
<label for="node-input-slaveId"><i class="fa fa-id-card"></i> Slave ID</label>
|
|
35
|
-
<input type="text" id="node-input-slaveId" placeholder="
|
|
35
|
+
<input type="text" id="node-input-slaveId" placeholder="e.g. 42 (or leave blank, set via msg.slaveId)">
|
|
36
36
|
</div>
|
|
37
37
|
<div class="form-row">
|
|
38
38
|
<label for="node-input-registerType"><i class="fa fa-list"></i> Register Type</label>
|
|
39
39
|
<select id="node-input-registerType">
|
|
40
|
-
<option value="holding"
|
|
40
|
+
<option value="holding">Holding Registers</option>
|
|
41
41
|
<option value="coil">Coils</option>
|
|
42
42
|
<option value="input">Input Registers</option>
|
|
43
43
|
<option value="discrete">Discrete Inputs</option>
|
|
@@ -53,25 +53,92 @@
|
|
|
53
53
|
</div>
|
|
54
54
|
<div class="form-row">
|
|
55
55
|
<label for="node-input-pollInterval"><i class="fa fa-clock-o"></i> Poll Interval (ms)</label>
|
|
56
|
-
<input type="number" id="node-input-pollInterval" min="0" placeholder="0 =
|
|
56
|
+
<input type="number" id="node-input-pollInterval" min="0" placeholder="0 = trigger only">
|
|
57
|
+
</div>
|
|
58
|
+
<div class="form-tips">
|
|
59
|
+
<b>Tip:</b> Override at runtime via <code>msg.slaveId</code>, <code>msg.address</code>,
|
|
60
|
+
<code>msg.quantity</code>, or <code>msg.registerType</code>.
|
|
57
61
|
</div>
|
|
58
62
|
</script>
|
|
59
63
|
|
|
60
64
|
<script type="text/html" data-help-name="modpackqt-slave-read">
|
|
61
|
-
<p>
|
|
62
|
-
|
|
65
|
+
<p>
|
|
66
|
+
Reads the current register values from a <b>ModPackQT Slave</b>. Use this to monitor or
|
|
67
|
+
verify what your slave is currently serving to external Modbus masters.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<h3>Quick Setup — 4 steps</h3>
|
|
71
|
+
<ol>
|
|
72
|
+
<li>
|
|
73
|
+
<b>Create a free account</b> at
|
|
74
|
+
<a href="https://modpackqt.com" target="_blank">modpackqt.com</a>
|
|
75
|
+
(slave feature requires a paid plan — see below).
|
|
76
|
+
</li>
|
|
77
|
+
<li>
|
|
78
|
+
<b>Download and run the Gateway app.</b>
|
|
79
|
+
The gateway exposes a local REST API (default port <code>8502</code>).
|
|
80
|
+
Open the ModPackQT app and click <em>Download Gateway</em>.
|
|
81
|
+
</li>
|
|
82
|
+
<li>
|
|
83
|
+
<b>Create a Slave</b> in the ModPackQT app:
|
|
84
|
+
go to <em>Slave Simulator</em> → <em>New Slave</em>, set a Modbus port (e.g. <code>502</code>),
|
|
85
|
+
then click <em>Start</em>. Copy the <b>Slave ID</b> shown in the app.
|
|
86
|
+
</li>
|
|
87
|
+
<li>
|
|
88
|
+
<b>Add a ModPackQT Config node</b> (click the pencil next to Gateway above),
|
|
89
|
+
set Host to <code>localhost</code> and Port to <code>8502</code>.
|
|
90
|
+
Paste your API key from <em>ModPackQT → Settings → API Keys</em>.
|
|
91
|
+
</li>
|
|
92
|
+
</ol>
|
|
93
|
+
|
|
94
|
+
<h3>Plan requirement</h3>
|
|
95
|
+
<p>
|
|
96
|
+
The Slave Simulator is a <b>paid-plan feature</b>.
|
|
97
|
+
Free accounts cannot use slave nodes. If you receive a <code>403</code> error,
|
|
98
|
+
upgrade your plan first.
|
|
99
|
+
<a href="https://modpackqt.com/#pricing" target="_blank">View plans & upgrade →</a>
|
|
100
|
+
</p>
|
|
101
|
+
|
|
63
102
|
<h3>Inputs</h3>
|
|
64
103
|
<dl class="message-properties">
|
|
65
104
|
<dt>payload <span class="property-type">any</span></dt>
|
|
66
|
-
<dd>Any message triggers a read (when Poll Interval is 0).</dd>
|
|
105
|
+
<dd>Any message triggers a read (when Poll Interval is 0 or disabled).</dd>
|
|
106
|
+
<dt class="optional">slaveId <span class="property-type">string | number</span></dt>
|
|
107
|
+
<dd>Overrides the Slave ID configured in the node.</dd>
|
|
108
|
+
<dt class="optional">address <span class="property-type">number</span></dt>
|
|
109
|
+
<dd>Overrides the start address.</dd>
|
|
110
|
+
<dt class="optional">quantity <span class="property-type">number</span></dt>
|
|
111
|
+
<dd>Overrides how many registers to read.</dd>
|
|
112
|
+
<dt class="optional">registerType <span class="property-type">string</span></dt>
|
|
113
|
+
<dd>Overrides the register type: <code>holding</code>, <code>coil</code>, <code>input</code>, or <code>discrete</code>.</dd>
|
|
67
114
|
</dl>
|
|
115
|
+
|
|
68
116
|
<h3>Outputs</h3>
|
|
69
117
|
<dl class="message-properties">
|
|
70
|
-
<dt>payload <span class="property-type">
|
|
71
|
-
<dd>
|
|
118
|
+
<dt>payload.values <span class="property-type">array</span></dt>
|
|
119
|
+
<dd>Array of register values (integers for holding/input, booleans for coil/discrete).</dd>
|
|
120
|
+
<dt>payload.registerType <span class="property-type">string</span></dt>
|
|
121
|
+
<dd>Register type that was read.</dd>
|
|
122
|
+
<dt>payload.address <span class="property-type">number</span></dt>
|
|
123
|
+
<dd>Start address that was read.</dd>
|
|
124
|
+
<dt>payload.quantity <span class="property-type">number</span></dt>
|
|
125
|
+
<dd>Number of values returned.</dd>
|
|
126
|
+
<dt>payload.slaveId <span class="property-type">string</span></dt>
|
|
127
|
+
<dd>The Slave ID that was queried.</dd>
|
|
72
128
|
<dt>topic <span class="property-type">string</span></dt>
|
|
73
129
|
<dd><code>slave/{slaveId}/{registerType}/{address}</code></dd>
|
|
74
130
|
</dl>
|
|
75
|
-
|
|
76
|
-
<
|
|
131
|
+
|
|
132
|
+
<h3>Status messages</h3>
|
|
133
|
+
<ul>
|
|
134
|
+
<li><b>yellow ring</b> — polling active, waiting for first response</li>
|
|
135
|
+
<li><b>green dot</b> — last read succeeded, shows register info + timestamp</li>
|
|
136
|
+
<li><b>red dot</b> — error (check API key / plan / gateway)</li>
|
|
137
|
+
<li><i>slave not found</i> — Slave ID is wrong or slave was deleted</li>
|
|
138
|
+
<li><i>gateway not running</i> — Gateway app is not started</li>
|
|
139
|
+
</ul>
|
|
140
|
+
|
|
141
|
+
<p>
|
|
142
|
+
<a href="https://modpackqt.com/node-red" target="_blank">Full documentation & example flows →</a>
|
|
143
|
+
</p>
|
|
77
144
|
</script>
|
|
@@ -6,39 +6,67 @@ module.exports = function(RED) {
|
|
|
6
6
|
|
|
7
7
|
node.slaveId = config.slaveId || '';
|
|
8
8
|
node.registerType = config.registerType || 'holding';
|
|
9
|
-
node.address = parseInt(config.address)
|
|
10
|
-
node.quantity = parseInt(config.quantity)
|
|
11
|
-
node.pollInterval = parseInt(config.pollInterval) || 0;
|
|
9
|
+
node.address = parseInt(config.address, 10) || 0;
|
|
10
|
+
node.quantity = parseInt(config.quantity, 10) || 1;
|
|
11
|
+
node.pollInterval = parseInt(config.pollInterval, 10) || 0;
|
|
12
12
|
|
|
13
13
|
let timer = null;
|
|
14
14
|
|
|
15
|
-
async function doRead() {
|
|
15
|
+
async function doRead(msg) {
|
|
16
16
|
if (!server) {
|
|
17
|
-
node.status({ fill: 'red', shape: 'ring', text: 'no config' });
|
|
17
|
+
node.status({ fill: 'red', shape: 'ring', text: 'no config — add a ModPackQT Config node' });
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
const slaveId = (msg && msg.slaveId) || node.slaveId;
|
|
22
|
+
if (!slaveId) {
|
|
21
23
|
node.status({ fill: 'red', shape: 'ring', text: 'slave ID required' });
|
|
24
|
+
node.error('No Slave ID set. Configure it in the node or send msg.slaveId.', msg);
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
27
|
+
|
|
28
|
+
const registerType = (msg && msg.registerType) || node.registerType;
|
|
29
|
+
const address = (msg && msg.address !== undefined) ? parseInt(msg.address, 10) : node.address;
|
|
30
|
+
const quantity = (msg && msg.quantity !== undefined) ? parseInt(msg.quantity, 10) : node.quantity;
|
|
31
|
+
|
|
24
32
|
try {
|
|
25
33
|
const result = await server.request(
|
|
26
34
|
'GET',
|
|
27
|
-
`/api/slaves/${encodeURIComponent(
|
|
35
|
+
`/api/slaves/${encodeURIComponent(slaveId)}/registers?type=${registerType}&offset=${address}&limit=${quantity}`
|
|
28
36
|
);
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
|
|
38
|
+
const values = result.values || [];
|
|
39
|
+
const now = new Date().toLocaleTimeString();
|
|
40
|
+
const pollTxt = node.pollInterval > 0 ? ` (poll ${node.pollInterval}ms)` : '';
|
|
41
|
+
node.status({ fill: 'green', shape: 'dot', text: `${registerType} @${address} [${values.length}] · ${now}${pollTxt}` });
|
|
42
|
+
|
|
43
|
+
node.send({
|
|
44
|
+
payload: {
|
|
45
|
+
values,
|
|
46
|
+
registerType,
|
|
47
|
+
address,
|
|
48
|
+
quantity: values.length,
|
|
49
|
+
total: result.total,
|
|
50
|
+
slaveId
|
|
51
|
+
},
|
|
52
|
+
topic: `slave/${slaveId}/${registerType}/${address}`
|
|
53
|
+
});
|
|
31
54
|
} catch (err) {
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
const hint = err.message.includes('403') ? 'check API key / plan limit'
|
|
56
|
+
: err.message.includes('404') ? 'slave not found — check Slave ID'
|
|
57
|
+
: err.message.includes('ECONNREFUSED') ? 'gateway not running'
|
|
58
|
+
: err.message;
|
|
59
|
+
node.status({ fill: 'red', shape: 'dot', text: hint });
|
|
60
|
+
node.error(err.message, msg);
|
|
34
61
|
}
|
|
35
62
|
}
|
|
36
63
|
|
|
37
64
|
if (node.pollInterval > 0) {
|
|
38
|
-
|
|
65
|
+
node.status({ fill: 'yellow', shape: 'ring', text: `polling every ${node.pollInterval}ms…` });
|
|
66
|
+
timer = setInterval(() => doRead(null), node.pollInterval);
|
|
39
67
|
}
|
|
40
68
|
|
|
41
|
-
node.on('input', async function() { await doRead(); });
|
|
69
|
+
node.on('input', async function(msg) { await doRead(msg); });
|
|
42
70
|
node.on('close', function() { if (timer) clearInterval(timer); });
|
|
43
71
|
}
|
|
44
72
|
RED.nodes.registerType('modpackqt-slave-read', ModPackQTSlaveReadNode);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: '' },
|
|
7
7
|
server: { value: '', type: 'modpackqt-config', required: true },
|
|
8
|
-
slaveId: { value: ''
|
|
8
|
+
slaveId: { value: '' },
|
|
9
9
|
registerType: { value: 'holding', required: true },
|
|
10
10
|
address: { value: 0, required: true, validate: RED.validators.number() }
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
outputs: 1,
|
|
14
14
|
icon: 'font-awesome/fa-hdd-o',
|
|
15
15
|
label: function() {
|
|
16
|
-
return this.name ||
|
|
16
|
+
return this.name || ('Slave Write ' + (this.registerType || 'holding') + ' @' + this.address);
|
|
17
17
|
},
|
|
18
18
|
paletteLabel: 'slave write'
|
|
19
19
|
});
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
<div class="form-row">
|
|
32
32
|
<label for="node-input-slaveId"><i class="fa fa-id-card"></i> Slave ID</label>
|
|
33
|
-
<input type="text" id="node-input-slaveId" placeholder="
|
|
33
|
+
<input type="text" id="node-input-slaveId" placeholder="e.g. 42 (or leave blank, set via msg.slaveId)">
|
|
34
34
|
</div>
|
|
35
35
|
<div class="form-row">
|
|
36
36
|
<label for="node-input-registerType"><i class="fa fa-list"></i> Register Type</label>
|
|
37
37
|
<select id="node-input-registerType">
|
|
38
|
-
<option value="holding"
|
|
39
|
-
<option value="coil">Coils</option>
|
|
38
|
+
<option value="holding">Holding Registers (FC16)</option>
|
|
39
|
+
<option value="coil">Coils (FC15)</option>
|
|
40
40
|
<option value="input">Input Registers</option>
|
|
41
41
|
<option value="discrete">Discrete Inputs</option>
|
|
42
42
|
</select>
|
|
@@ -45,21 +45,94 @@
|
|
|
45
45
|
<label for="node-input-address"><i class="fa fa-map-marker"></i> Start Address</label>
|
|
46
46
|
<input type="number" id="node-input-address" min="0" max="65535" placeholder="0">
|
|
47
47
|
</div>
|
|
48
|
+
<div class="form-tips">
|
|
49
|
+
<b>Tip:</b> Send <code>msg.slaveId</code> to override the Slave ID at runtime.<br>
|
|
50
|
+
Send <code>msg.address</code> to override the start address.<br>
|
|
51
|
+
Payload: number, array, or JSON string <code>"[100,200,300]"</code>
|
|
52
|
+
</div>
|
|
48
53
|
</script>
|
|
49
54
|
|
|
50
55
|
<script type="text/html" data-help-name="modpackqt-slave-write">
|
|
51
|
-
<p>
|
|
52
|
-
|
|
56
|
+
<p>
|
|
57
|
+
Push values into a <b>ModPackQT Slave</b> register table. Any external Modbus master that
|
|
58
|
+
connects to that slave will read whatever values you last wrote here.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<h3>Quick Setup — 4 steps</h3>
|
|
62
|
+
<ol>
|
|
63
|
+
<li>
|
|
64
|
+
<b>Create a free account</b> at
|
|
65
|
+
<a href="https://modpackqt.com" target="_blank">modpackqt.com</a>
|
|
66
|
+
(slave feature requires a paid plan — see below).
|
|
67
|
+
</li>
|
|
68
|
+
<li>
|
|
69
|
+
<b>Download and run the Gateway app.</b>
|
|
70
|
+
The gateway exposes a local REST API (default port <code>8502</code>) that this
|
|
71
|
+
node talks to. Open the ModPackQT app and click <em>Download Gateway</em>.
|
|
72
|
+
</li>
|
|
73
|
+
<li>
|
|
74
|
+
<b>Create a Slave</b> in the ModPackQT app:
|
|
75
|
+
go to <em>Slave Simulator</em> → <em>New Slave</em>, set a Modbus port (e.g. <code>502</code>),
|
|
76
|
+
then click <em>Start</em>. Copy the <b>Slave ID</b> shown in the app.
|
|
77
|
+
</li>
|
|
78
|
+
<li>
|
|
79
|
+
<b>Add a ModPackQT Config node</b> (click the pencil next to Gateway above),
|
|
80
|
+
set Host to <code>localhost</code> and Port to <code>8502</code>.
|
|
81
|
+
Paste your API key from <em>ModPackQT → Settings → API Keys</em>.
|
|
82
|
+
</li>
|
|
83
|
+
</ol>
|
|
84
|
+
|
|
85
|
+
<h3>Plan requirement</h3>
|
|
86
|
+
<p>
|
|
87
|
+
The Slave Simulator is a <b>paid-plan feature</b>.
|
|
88
|
+
Free accounts cannot create slaves. If you receive a <code>403</code> error,
|
|
89
|
+
your plan does not include slaves or you have reached your slave limit.
|
|
90
|
+
<a href="https://modpackqt.com/#pricing" target="_blank">View plans & upgrade →</a>
|
|
91
|
+
</p>
|
|
92
|
+
|
|
53
93
|
<h3>Inputs</h3>
|
|
54
94
|
<dl class="message-properties">
|
|
55
|
-
<dt>payload <span class="property-type">number | array</span></dt>
|
|
56
|
-
<dd>
|
|
95
|
+
<dt>payload <span class="property-type">number | array | string</span></dt>
|
|
96
|
+
<dd>
|
|
97
|
+
Value(s) to write. Accepts: a single number (<code>42</code>),
|
|
98
|
+
an array (<code>[10, 20, 30]</code>), or a JSON string (<code>"[10,20,30]"</code>).
|
|
99
|
+
All values are converted to integers automatically.
|
|
100
|
+
</dd>
|
|
101
|
+
<dt class="optional">slaveId <span class="property-type">string | number</span></dt>
|
|
102
|
+
<dd>Overrides the Slave ID configured in the node editor.</dd>
|
|
103
|
+
<dt class="optional">address <span class="property-type">number</span></dt>
|
|
104
|
+
<dd>Overrides the start address configured in the node editor.</dd>
|
|
105
|
+
<dt class="optional">registerType <span class="property-type">string</span></dt>
|
|
106
|
+
<dd>Overrides the register type: <code>holding</code>, <code>coil</code>, <code>input</code>, or <code>discrete</code>.</dd>
|
|
57
107
|
</dl>
|
|
108
|
+
|
|
58
109
|
<h3>Outputs</h3>
|
|
59
110
|
<dl class="message-properties">
|
|
60
111
|
<dt>payload <span class="property-type">any</span></dt>
|
|
61
|
-
<dd>Original message passed through
|
|
112
|
+
<dd>Original message passed through unchanged.</dd>
|
|
113
|
+
<dt>success <span class="property-type">boolean</span></dt>
|
|
114
|
+
<dd><code>true</code> when the write succeeded.</dd>
|
|
115
|
+
<dt>valuesWritten <span class="property-type">array</span></dt>
|
|
116
|
+
<dd>The integer values that were written.</dd>
|
|
62
117
|
</dl>
|
|
63
|
-
|
|
64
|
-
<
|
|
118
|
+
|
|
119
|
+
<h3>Status messages</h3>
|
|
120
|
+
<ul>
|
|
121
|
+
<li><b>blue ring</b> — write in progress</li>
|
|
122
|
+
<li><b>green dot</b> — write succeeded, shows count + timestamp</li>
|
|
123
|
+
<li><b>red dot</b> — error (check API key / plan / gateway)</li>
|
|
124
|
+
<li><i>slave not found</i> — Slave ID is wrong or slave was deleted</li>
|
|
125
|
+
<li><i>gateway not running</i> — Gateway app is not started</li>
|
|
126
|
+
</ul>
|
|
127
|
+
|
|
128
|
+
<h3>Example use case</h3>
|
|
129
|
+
<p>
|
|
130
|
+
Read temperature from an MQTT topic → convert to integer → send to this node.
|
|
131
|
+
Any PLC or SCADA system on the network can then read that temperature via
|
|
132
|
+
standard Modbus TCP from the ModPackQT slave port.
|
|
133
|
+
</p>
|
|
134
|
+
|
|
135
|
+
<p>
|
|
136
|
+
<a href="https://modpackqt.com/node-red" target="_blank">Full documentation & example flows →</a>
|
|
137
|
+
</p>
|
|
65
138
|
</script>
|
|
@@ -6,29 +6,56 @@ module.exports = function(RED) {
|
|
|
6
6
|
|
|
7
7
|
node.slaveId = config.slaveId || '';
|
|
8
8
|
node.registerType = config.registerType || 'holding';
|
|
9
|
-
node.address = parseInt(config.address) || 0;
|
|
9
|
+
node.address = parseInt(config.address, 10) || 0;
|
|
10
10
|
|
|
11
11
|
node.on('input', async function(msg) {
|
|
12
12
|
if (!server) {
|
|
13
|
-
node.status({ fill: 'red', shape: 'ring', text: 'no config' });
|
|
13
|
+
node.status({ fill: 'red', shape: 'ring', text: 'no config — add a ModPackQT Config node' });
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
const slaveId = msg.slaveId || node.slaveId;
|
|
18
|
+
if (!slaveId) {
|
|
17
19
|
node.status({ fill: 'red', shape: 'ring', text: 'slave ID required' });
|
|
20
|
+
node.error('No Slave ID set. Configure it in the node or send msg.slaveId.', msg);
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
23
|
+
|
|
20
24
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
// Accept string JSON ("[100,200]"), plain number string, actual array, or number
|
|
26
|
+
let raw = msg.payload;
|
|
27
|
+
if (typeof raw === 'string') {
|
|
28
|
+
try { raw = JSON.parse(raw); } catch (_) { raw = Number(raw); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Always convert every value to an integer to avoid encoding corruption
|
|
32
|
+
const values = Array.isArray(raw)
|
|
33
|
+
? raw.map(v => parseInt(v, 10))
|
|
34
|
+
: [parseInt(raw, 10)];
|
|
35
|
+
|
|
36
|
+
if (values.some(v => isNaN(v))) {
|
|
37
|
+
throw new Error(`Invalid payload — expected number or array of numbers, got: ${JSON.stringify(msg.payload)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
node.status({ fill: 'blue', shape: 'ring', text: `writing ${values.length} value(s)…` });
|
|
41
|
+
|
|
42
|
+
await server.request('PATCH', `/api/slaves/${encodeURIComponent(slaveId)}/registers`, {
|
|
43
|
+
registerType: msg.registerType || node.registerType,
|
|
44
|
+
address: msg.address !== undefined ? parseInt(msg.address, 10) : node.address,
|
|
25
45
|
values
|
|
26
46
|
});
|
|
27
|
-
|
|
47
|
+
|
|
48
|
+
const now = new Date().toLocaleTimeString();
|
|
49
|
+
node.status({ fill: 'green', shape: 'dot', text: `wrote ${values.length} @ addr ${node.address} · ${now}` });
|
|
28
50
|
msg.success = true;
|
|
51
|
+
msg.valuesWritten = values;
|
|
29
52
|
node.send(msg);
|
|
30
53
|
} catch (err) {
|
|
31
|
-
|
|
54
|
+
const hint = err.message.includes('403') ? 'check API key / plan limit'
|
|
55
|
+
: err.message.includes('404') ? 'slave not found — check Slave ID'
|
|
56
|
+
: err.message.includes('ECONNREFUSED') ? 'gateway not running'
|
|
57
|
+
: err.message;
|
|
58
|
+
node.status({ fill: 'red', shape: 'dot', text: hint });
|
|
32
59
|
node.error(err.message, msg);
|
|
33
60
|
}
|
|
34
61
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-modbus-modpackqt",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Node-RED nodes for ModPackQT — read/write Modbus TCP registers (master & slave) via the ModPackQT Gateway app. Supports FC1/FC2/FC3/FC4 reads and FC5/FC6/FC15/FC16 writes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|