node-red-contrib-modbus-modpackqt 3.1.1 → 3.2.1
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/CHANGELOG.md +41 -0
- package/README.md +1 -0
- package/nodes/lib/cloud-client.js +58 -0
- package/nodes/lib/probe-runtime.js +1 -1
- package/nodes/modpackqt-config.html +15 -6
- package/nodes/modpackqt-config.js +25 -0
- package/nodes/modpackqt-master-probe.html +34 -12
- package/nodes/modpackqt-master-read.html +100 -15
- package/nodes/modpackqt-master-write.html +35 -15
- package/nodes/modpackqt-slave-probe.html +74 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ All notable changes to **node-red-contrib-modbus-modpackqt** are documented
|
|
|
4
4
|
here. This project follows [Semantic Versioning](https://semver.org/) — pin a
|
|
5
5
|
major version (`^2.0.0`) in production.
|
|
6
6
|
|
|
7
|
+
## [3.2.1] — 2026-05-10
|
|
8
|
+
|
|
9
|
+
### Changed — Picker-first node UI
|
|
10
|
+
|
|
11
|
+
- **"My Profiles" / "My Slaves" dropdown is now the primary input** on
|
|
12
|
+
master-read, master-write, master-probe and slave-probe. Pick a saved
|
|
13
|
+
device from your modpackqt.com account and the node is configured.
|
|
14
|
+
- **Runtime config + manual Host/Port/Unit/Timeout fields are now
|
|
15
|
+
hidden** under a small **▸ Advanced — runtime config & manual entry**
|
|
16
|
+
toggle. Auto-expanded for first-time setup (no runtime configured),
|
|
17
|
+
collapsed once you have a runtime + Account Key set up.
|
|
18
|
+
- No behaviour change for existing flows — fields, defaults and
|
|
19
|
+
validation are identical to v3.2.0.
|
|
20
|
+
|
|
21
|
+
## [3.2.0] — 2026-05-10
|
|
22
|
+
|
|
23
|
+
### Added — Cloud profile pickers
|
|
24
|
+
|
|
25
|
+
- **"My Profiles" dropdown** on `modpackqt-master-read`,
|
|
26
|
+
`modpackqt-master-write` and `modpackqt-master-probe`. Picks one of
|
|
27
|
+
your saved Modbus TCP profiles from your modpackqt.com account and
|
|
28
|
+
auto-fills `targetHost`, `targetPort` and `unitId`. No more retyping
|
|
29
|
+
IPs across nodes.
|
|
30
|
+
- **"My Slaves" dropdown** on `modpackqt-slave-probe`. Picks one of
|
|
31
|
+
your saved slave configs and auto-fills `bindPort` + `unitId`.
|
|
32
|
+
- **Account Key** field on the runtime config (renamed from "API Key").
|
|
33
|
+
Paste your tunnel key from
|
|
34
|
+
[modpackqt.com → Settings → Cloud Gateways](https://modpackqt.com/settings)
|
|
35
|
+
to enable the dropdowns. Same key the Electron gateway uses — no new
|
|
36
|
+
signup, no new mechanism.
|
|
37
|
+
- **Hidden admin proxy** (`/modpackqt/connections`, `/modpackqt/slaves`)
|
|
38
|
+
on the Node-RED admin port. Calls modpackqt.com server-side from the
|
|
39
|
+
Node-RED process so the Account Key never reaches the browser.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Manual `targetHost` / `targetPort` / `unitId` fields stay as a fallback
|
|
44
|
+
on every node — existing v3.1.x flows keep working untouched.
|
|
45
|
+
- Runtime config `apiKey` credential is unchanged on disk — only the
|
|
46
|
+
label and helper text in the editor were updated.
|
|
47
|
+
|
|
7
48
|
## [3.1.1] — 2026-05-10
|
|
8
49
|
|
|
9
50
|
### Fixed
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ By [ModPackQT](https://modpackqt.com).
|
|
|
16
16
|
- **Modbus master** — read (FC1–FC4) and write (FC5/FC6/FC15/FC16) over **TCP** or **RTU (serial)**
|
|
17
17
|
- **Embedded Modbus TCP slave server** — push values from any flow, let PLCs / SCADA / HMIs read them
|
|
18
18
|
- **Passive traffic monitor** — see every Modbus op (timing, values, errors) in real time
|
|
19
|
+
- **Optional cloud profile pickers** *(v3.2.0)* — paste your ModPackQT Account Key into the runtime config and master / probe nodes show a dropdown of your saved Modbus profiles. No more retyping IPs across nodes.
|
|
19
20
|
- **Outputs raw register values** — pair with [`node-red-contrib-bytes-modpackqt`](https://www.npmjs.com/package/node-red-contrib-bytes-modpackqt) to decode int / float / string / bitmask
|
|
20
21
|
- **Zero external dependencies** — Modbus runs inside the Node-RED process
|
|
21
22
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModPackQT Cloud Client — minimal HTTPS reader for the user's saved
|
|
3
|
+
* Modbus profiles & slaves at modpackqt.com.
|
|
4
|
+
*
|
|
5
|
+
* Reuses the existing X-Tunnel-Key auth that the Electron gateway already
|
|
6
|
+
* uses. The user's "Account Key" is the same tunnel key they generate at
|
|
7
|
+
* modpackqt.com → Settings → Cloud Gateways. No new auth, no new dep.
|
|
8
|
+
*
|
|
9
|
+
* All requests are read-only GETs. The Node-RED admin endpoint in
|
|
10
|
+
* modpackqt-config.js wraps these so the editor never sees the key
|
|
11
|
+
* (proxied server-side from the Node-RED process).
|
|
12
|
+
*/
|
|
13
|
+
const https = require('https');
|
|
14
|
+
const http = require('http');
|
|
15
|
+
const { URL } = require('url');
|
|
16
|
+
|
|
17
|
+
const ORIGIN = process.env.MODPACKQT_CLOUD_ORIGIN || 'https://modpackqt.com';
|
|
18
|
+
const TIMEOUT_MS = 10000;
|
|
19
|
+
|
|
20
|
+
function fetchJson(path, key, callback) {
|
|
21
|
+
let u;
|
|
22
|
+
try { u = new URL(path, ORIGIN); }
|
|
23
|
+
catch (e) { return callback(new Error('Invalid cloud URL: ' + e.message)); }
|
|
24
|
+
|
|
25
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
26
|
+
const req = lib.request({
|
|
27
|
+
method: 'GET',
|
|
28
|
+
hostname: u.hostname,
|
|
29
|
+
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
30
|
+
path: u.pathname + (u.search || ''),
|
|
31
|
+
headers: {
|
|
32
|
+
'X-Tunnel-Key': key,
|
|
33
|
+
'Accept': 'application/json',
|
|
34
|
+
'User-Agent': 'node-red-contrib-modbus-modpackqt'
|
|
35
|
+
},
|
|
36
|
+
timeout: TIMEOUT_MS
|
|
37
|
+
}, (res) => {
|
|
38
|
+
let body = '';
|
|
39
|
+
res.setEncoding('utf8');
|
|
40
|
+
res.on('data', (c) => { body += c; });
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
if (res.statusCode >= 400) {
|
|
43
|
+
return callback(new Error('Cloud HTTP ' + res.statusCode + ': ' + body.slice(0, 200)));
|
|
44
|
+
}
|
|
45
|
+
try { callback(null, JSON.parse(body)); }
|
|
46
|
+
catch (e) { callback(new Error('Invalid JSON from cloud: ' + e.message)); }
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
req.on('error', (err) => callback(err));
|
|
50
|
+
req.on('timeout', () => { req.destroy(new Error('Cloud request timed out after ' + TIMEOUT_MS + 'ms')); });
|
|
51
|
+
req.end();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
origin: () => ORIGIN,
|
|
56
|
+
fetchConnections: (key, cb) => fetchJson('/api/connections', key, cb),
|
|
57
|
+
fetchSlaves: (key, cb) => fetchJson('/api/slaves', key, cb),
|
|
58
|
+
};
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const http = require('http');
|
|
19
19
|
const { URL } = require('url');
|
|
20
20
|
|
|
21
|
-
const PALETTE_VERSION = '3.
|
|
21
|
+
const PALETTE_VERSION = '3.2.1';
|
|
22
22
|
const DEFAULT_PORT = parseInt(process.env.MODPACKQT_PROBE_PORT, 10) || 8502;
|
|
23
23
|
const BIND_HOST = process.env.MODPACKQT_PROBE_HOST || '127.0.0.1';
|
|
24
24
|
const PORT_RETRY = 5;
|
|
@@ -102,10 +102,15 @@
|
|
|
102
102
|
<input type="number" id="node-config-input-slavePort" placeholder="1502">
|
|
103
103
|
</div>
|
|
104
104
|
|
|
105
|
-
<h4 style="margin-top:18px">
|
|
105
|
+
<h4 style="margin-top:18px">ModPackQT Account Key (optional — for cloud profile sync)</h4>
|
|
106
106
|
<div class="form-row">
|
|
107
|
-
<label for="node-config-input-apiKey"><i class="fa fa-key"></i>
|
|
108
|
-
<input type="password" id="node-config-input-apiKey" placeholder="Optional —
|
|
107
|
+
<label for="node-config-input-apiKey"><i class="fa fa-key"></i> Account Key</label>
|
|
108
|
+
<input type="password" id="node-config-input-apiKey" placeholder="Optional — paste your tunnel key for profile pickers">
|
|
109
|
+
</div>
|
|
110
|
+
<div class="form-tips" style="font-size:11px;color:#6b7280;margin-top:-6px">
|
|
111
|
+
With a key set, master & probe nodes show a dropdown of your saved Modbus profiles
|
|
112
|
+
and slaves from <a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
|
|
113
|
+
All Modbus runs locally — the key is only used for read-only profile sync.
|
|
109
114
|
</div>
|
|
110
115
|
|
|
111
116
|
<div class="form-tips">
|
|
@@ -143,9 +148,13 @@
|
|
|
143
148
|
last wrote.
|
|
144
149
|
</p>
|
|
145
150
|
|
|
146
|
-
<h3>
|
|
151
|
+
<h3>ModPackQT Account Key (optional)</h3>
|
|
147
152
|
<p>
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
Same tunnel key the Electron gateway uses — generate one at
|
|
154
|
+
<a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
|
|
155
|
+
With a key set, the master and probe nodes show a dropdown of your saved Modbus
|
|
156
|
+
profiles & slaves so you don't have to retype IPs and unit IDs. All Modbus traffic
|
|
157
|
+
still runs locally — the key is only used for a read-only HTTPS call from this
|
|
158
|
+
Node-RED process to load your profile list.
|
|
150
159
|
</p>
|
|
151
160
|
</script>
|
|
@@ -8,6 +8,31 @@
|
|
|
8
8
|
module.exports = function (RED) {
|
|
9
9
|
const ModbusRTU = require('modbus-serial');
|
|
10
10
|
const { EventEmitter } = require('events');
|
|
11
|
+
const cloudClient = require('./lib/cloud-client');
|
|
12
|
+
|
|
13
|
+
// ── Admin proxy endpoints — let the editor read the user's saved profiles
|
|
14
|
+
// and slaves from modpackqt.com without ever exposing the Account Key to
|
|
15
|
+
// the browser. Same X-Tunnel-Key the Electron gateway already uses.
|
|
16
|
+
// Registered once per Node-RED process.
|
|
17
|
+
if (!RED._modpackqtAdminRoutesRegistered) {
|
|
18
|
+
RED._modpackqtAdminRoutesRegistered = true;
|
|
19
|
+
function makeProxy(fetchFn) {
|
|
20
|
+
return function (req, res) {
|
|
21
|
+
const configId = req.query && req.query.config;
|
|
22
|
+
if (!configId) return res.status(400).json({ error: 'config query param required' });
|
|
23
|
+
const cfg = RED.nodes.getNode(configId);
|
|
24
|
+
if (!cfg) return res.status(404).json({ error: 'Runtime config not found (deploy first?)' });
|
|
25
|
+
const key = cfg.credentials && cfg.credentials.apiKey;
|
|
26
|
+
if (!key) return res.status(400).json({ error: 'No Account Key set on the runtime config — paste it in the modpackqt-config node first.' });
|
|
27
|
+
fetchFn(key, function (err, data) {
|
|
28
|
+
if (err) return res.status(502).json({ error: err.message });
|
|
29
|
+
res.json(data);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
RED.httpAdmin.get('/modpackqt/connections', RED.auth.needsPermission('flows.read'), makeProxy(cloudClient.fetchConnections));
|
|
34
|
+
RED.httpAdmin.get('/modpackqt/slaves', RED.auth.needsPermission('flows.read'), makeProxy(cloudClient.fetchSlaves));
|
|
35
|
+
}
|
|
11
36
|
|
|
12
37
|
function ModPackQTConfigNode(config) {
|
|
13
38
|
RED.nodes.createNode(this, config);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
color: '#7c3aed',
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: '' },
|
|
7
|
+
server: { value: '', type: 'modpackqt-config' },
|
|
7
8
|
targetHost: { value: '192.168.1.10', required: true },
|
|
8
9
|
targetPort: { value: 502, required: true, validate: RED.validators.number() },
|
|
9
10
|
unitId: { value: 1, required: true, validate: RED.validators.number() },
|
|
@@ -18,6 +19,8 @@
|
|
|
18
19
|
paletteLabel: 'modbus master probe',
|
|
19
20
|
oneditprepare: function () {
|
|
20
21
|
const node = this;
|
|
22
|
+
if (window.ModPackQTMasterPicker) ModPackQTMasterPicker.attach(this);
|
|
23
|
+
if (window.ModPackQTAdvancedToggle) ModPackQTAdvancedToggle.attach('modpackqt-master-probe');
|
|
21
24
|
const buildLink = (info) => {
|
|
22
25
|
const probeHost = (info && info.host) || '127.0.0.1';
|
|
23
26
|
const probePort = (info && info.port) || 8502;
|
|
@@ -47,22 +50,41 @@
|
|
|
47
50
|
<input type="text" id="node-input-name" placeholder="(optional, e.g. Inverter A)">
|
|
48
51
|
</div>
|
|
49
52
|
<div class="form-row">
|
|
50
|
-
<label for="node-input-
|
|
51
|
-
<
|
|
53
|
+
<label for="node-input-modpackqt-picker"><i class="fa fa-cloud-download"></i> My Profiles</label>
|
|
54
|
+
<select id="node-input-modpackqt-picker" style="width:65%"></select>
|
|
55
|
+
<button type="button" id="modpackqt-picker-refresh" class="red-ui-button" title="Reload from modpackqt.com" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
|
|
52
56
|
</div>
|
|
53
|
-
<div class="form-
|
|
54
|
-
|
|
55
|
-
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
57
|
+
<div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
|
|
58
|
+
Pick a saved Modbus profile from your modpackqt.com account — auto-fills host/port/unit. Set up your Account Key under <b>Advanced</b> below.
|
|
56
59
|
</div>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
+
|
|
61
|
+
<div class="form-row" style="margin:14px 0 4px 0;border-top:1px solid #e5e7eb;padding-top:10px">
|
|
62
|
+
<a href="#" class="modpackqt-advanced-toggle" style="font-size:12px;color:#6b7280;text-decoration:none;cursor:pointer">▸ Advanced — runtime config & manual entry</a>
|
|
60
63
|
</div>
|
|
61
|
-
<div class="
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
+
<div class="modpackqt-advanced" style="display:none">
|
|
65
|
+
<div class="form-row">
|
|
66
|
+
<label for="node-input-server"><i class="fa fa-cog"></i> Runtime</label>
|
|
67
|
+
<input type="text" id="node-input-server">
|
|
68
|
+
</div>
|
|
69
|
+
<div class="form-row">
|
|
70
|
+
<label for="node-input-targetHost"><i class="fa fa-plug"></i> Target Host</label>
|
|
71
|
+
<input type="text" id="node-input-targetHost" placeholder="192.168.1.10">
|
|
72
|
+
</div>
|
|
73
|
+
<div class="form-row">
|
|
74
|
+
<label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
|
|
75
|
+
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
76
|
+
</div>
|
|
77
|
+
<div class="form-row">
|
|
78
|
+
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
79
|
+
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
80
|
+
</div>
|
|
81
|
+
<div class="form-row">
|
|
82
|
+
<label for="node-input-timeoutMs"><i class="fa fa-clock-o"></i> Timeout (ms)</label>
|
|
83
|
+
<input type="number" id="node-input-timeoutMs" placeholder="3000">
|
|
84
|
+
</div>
|
|
64
85
|
</div>
|
|
65
|
-
|
|
86
|
+
|
|
87
|
+
<div class="form-tips" style="margin-top:14px">
|
|
66
88
|
<b>One probe = one device.</b> Drop additional master-probe nodes for additional devices —
|
|
67
89
|
the web console aggregates them all into a unified sidebar.
|
|
68
90
|
</div>
|
|
@@ -19,31 +19,93 @@
|
|
|
19
19
|
label: function () {
|
|
20
20
|
return this.name || `Master Read FC${this.functionCode} @${this.address}`;
|
|
21
21
|
},
|
|
22
|
-
paletteLabel: 'modbus master read'
|
|
22
|
+
paletteLabel: 'modbus master read',
|
|
23
|
+
oneditprepare: function () {
|
|
24
|
+
ModPackQTMasterPicker.attach(this);
|
|
25
|
+
if (window.ModPackQTAdvancedToggle) ModPackQTAdvancedToggle.attach('modpackqt-master-read');
|
|
26
|
+
}
|
|
23
27
|
});
|
|
24
28
|
</script>
|
|
25
29
|
|
|
30
|
+
<!-- Shared picker helper — loaded once per editor session -->
|
|
31
|
+
<script type="text/javascript">
|
|
32
|
+
if (!window.ModPackQTMasterPicker) {
|
|
33
|
+
window.ModPackQTMasterPicker = {
|
|
34
|
+
attach: function (node) {
|
|
35
|
+
const $sel = $('#node-input-modpackqt-picker');
|
|
36
|
+
if (!$sel.length) return;
|
|
37
|
+
const reload = function () {
|
|
38
|
+
const cfgId = $('#node-input-server').val();
|
|
39
|
+
$sel.empty().append('<option value="">— Loading… —</option>');
|
|
40
|
+
if (!cfgId || cfgId === '_ADD_') {
|
|
41
|
+
$sel.empty().append('<option value="">— Set up runtime & Account Key under Advanced —</option>');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
$.getJSON('modpackqt/connections?config=' + encodeURIComponent(cfgId))
|
|
45
|
+
.done(function (rows) {
|
|
46
|
+
$sel.empty().append('<option value="">— Manual entry (see Advanced) —</option>');
|
|
47
|
+
(rows || [])
|
|
48
|
+
.filter(function (c) { return c && (c.connectionType === 'tcp' || !c.connectionType); })
|
|
49
|
+
.forEach(function (c) {
|
|
50
|
+
const label = (c.name || '(unnamed)') + ' — ' + (c.host || '?') + ':' + (c.port || 502) + ' #' + (c.unitId || 1);
|
|
51
|
+
$('<option>').val(c.id).text(label).data('row', c).appendTo($sel);
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
.fail(function (xhr) {
|
|
55
|
+
const msg = (xhr.responseJSON && xhr.responseJSON.error) || ('HTTP ' + xhr.status);
|
|
56
|
+
$sel.empty().append($('<option>').val('').text('— ' + msg + ' —'));
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
$('#node-input-server').on('change', reload);
|
|
60
|
+
$('#modpackqt-picker-refresh').on('click', function (e) { e.preventDefault(); reload(); });
|
|
61
|
+
$sel.on('change', function () {
|
|
62
|
+
const row = $(this).find('option:selected').data('row');
|
|
63
|
+
if (!row) return;
|
|
64
|
+
$('#node-input-targetHost').val(row.host || '');
|
|
65
|
+
$('#node-input-targetPort').val(row.port || 502);
|
|
66
|
+
$('#node-input-unitId').val(row.unitId || 1);
|
|
67
|
+
});
|
|
68
|
+
reload();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (!window.ModPackQTAdvancedToggle) {
|
|
73
|
+
window.ModPackQTAdvancedToggle = {
|
|
74
|
+
attach: function (/* nodeType */) {
|
|
75
|
+
const $adv = $('.modpackqt-advanced');
|
|
76
|
+
const $tog = $('.modpackqt-advanced-toggle');
|
|
77
|
+
if (!$adv.length || !$tog.length) return;
|
|
78
|
+
const setOpen = function (open) {
|
|
79
|
+
$adv.toggle(open);
|
|
80
|
+
$tog.html((open ? '▾ Hide advanced' : '▸ Advanced — runtime config & manual entry'));
|
|
81
|
+
};
|
|
82
|
+
// Auto-expand if runtime not yet configured
|
|
83
|
+
const cfgId = $('#node-input-server').val();
|
|
84
|
+
const needsSetup = !cfgId || cfgId === '' || cfgId === '_ADD_';
|
|
85
|
+
setOpen(needsSetup);
|
|
86
|
+
$tog.off('click.modpackqt').on('click.modpackqt', function (e) {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
setOpen($adv.is(':hidden'));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
26
95
|
<script type="text/html" data-template-name="modpackqt-master-read">
|
|
27
96
|
<div class="form-row">
|
|
28
97
|
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
29
98
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
30
99
|
</div>
|
|
31
100
|
<div class="form-row">
|
|
32
|
-
<label for="node-input-
|
|
33
|
-
<
|
|
101
|
+
<label for="node-input-modpackqt-picker"><i class="fa fa-cloud-download"></i> My Profiles</label>
|
|
102
|
+
<select id="node-input-modpackqt-picker" style="width:65%"></select>
|
|
103
|
+
<button type="button" id="modpackqt-picker-refresh" class="red-ui-button" title="Reload from modpackqt.com" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
|
|
34
104
|
</div>
|
|
35
|
-
<div class="form-
|
|
36
|
-
|
|
37
|
-
<input type="text" id="node-input-targetHost" placeholder="Modbus device IP/hostname (TCP only)">
|
|
38
|
-
</div>
|
|
39
|
-
<div class="form-row">
|
|
40
|
-
<label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
|
|
41
|
-
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
42
|
-
</div>
|
|
43
|
-
<div class="form-row">
|
|
44
|
-
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
45
|
-
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
105
|
+
<div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
|
|
106
|
+
Pick a saved Modbus profile from your modpackqt.com account — auto-fills host/port/unit. Set up your Account Key under <b>Advanced</b> below.
|
|
46
107
|
</div>
|
|
108
|
+
|
|
47
109
|
<div class="form-row">
|
|
48
110
|
<label for="node-input-functionCode"><i class="fa fa-list"></i> Function Code</label>
|
|
49
111
|
<select id="node-input-functionCode">
|
|
@@ -65,7 +127,30 @@
|
|
|
65
127
|
<label for="node-input-pollInterval"><i class="fa fa-clock-o"></i> Poll Interval (ms)</label>
|
|
66
128
|
<input type="number" id="node-input-pollInterval" min="0" placeholder="0 = disabled">
|
|
67
129
|
</div>
|
|
68
|
-
|
|
130
|
+
|
|
131
|
+
<div class="form-row" style="margin:14px 0 4px 0;border-top:1px solid #e5e7eb;padding-top:10px">
|
|
132
|
+
<a href="#" class="modpackqt-advanced-toggle" style="font-size:12px;color:#6b7280;text-decoration:none;cursor:pointer">▸ Advanced — runtime config & manual entry</a>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="modpackqt-advanced" style="display:none">
|
|
135
|
+
<div class="form-row">
|
|
136
|
+
<label for="node-input-server"><i class="fa fa-cog"></i> Runtime</label>
|
|
137
|
+
<input type="text" id="node-input-server">
|
|
138
|
+
</div>
|
|
139
|
+
<div class="form-row">
|
|
140
|
+
<label for="node-input-targetHost"><i class="fa fa-plug"></i> Target Host</label>
|
|
141
|
+
<input type="text" id="node-input-targetHost" placeholder="Modbus device IP/hostname (TCP only)">
|
|
142
|
+
</div>
|
|
143
|
+
<div class="form-row">
|
|
144
|
+
<label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
|
|
145
|
+
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
146
|
+
</div>
|
|
147
|
+
<div class="form-row">
|
|
148
|
+
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
149
|
+
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="form-tips" style="margin-top:14px">
|
|
69
154
|
<b>Output:</b> <code>msg.payload</code> = raw register array. To decode int / float / string,
|
|
70
155
|
pair with <a href="https://www.npmjs.com/package/node-red-contrib-bytes-modpackqt" target="_blank">node-red-contrib-bytes-modpackqt</a>.
|
|
71
156
|
</div>
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
label: function () {
|
|
18
18
|
return this.name || `Master Write FC${this.functionCode} @${this.address}`;
|
|
19
19
|
},
|
|
20
|
-
paletteLabel: 'modbus master write'
|
|
20
|
+
paletteLabel: 'modbus master write',
|
|
21
|
+
oneditprepare: function () {
|
|
22
|
+
if (window.ModPackQTMasterPicker) ModPackQTMasterPicker.attach(this);
|
|
23
|
+
if (window.ModPackQTAdvancedToggle) ModPackQTAdvancedToggle.attach('modpackqt-master-write');
|
|
24
|
+
}
|
|
21
25
|
});
|
|
22
26
|
</script>
|
|
23
27
|
|
|
@@ -27,21 +31,14 @@
|
|
|
27
31
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
28
32
|
</div>
|
|
29
33
|
<div class="form-row">
|
|
30
|
-
<label for="node-input-
|
|
31
|
-
<
|
|
34
|
+
<label for="node-input-modpackqt-picker"><i class="fa fa-cloud-download"></i> My Profiles</label>
|
|
35
|
+
<select id="node-input-modpackqt-picker" style="width:65%"></select>
|
|
36
|
+
<button type="button" id="modpackqt-picker-refresh" class="red-ui-button" title="Reload from modpackqt.com" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
|
|
32
37
|
</div>
|
|
33
|
-
<div class="form-
|
|
34
|
-
|
|
35
|
-
<input type="text" id="node-input-targetHost" placeholder="Modbus device IP/hostname (TCP only)">
|
|
36
|
-
</div>
|
|
37
|
-
<div class="form-row">
|
|
38
|
-
<label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
|
|
39
|
-
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
40
|
-
</div>
|
|
41
|
-
<div class="form-row">
|
|
42
|
-
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
43
|
-
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
38
|
+
<div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
|
|
39
|
+
Pick a saved Modbus profile from your modpackqt.com account — auto-fills host/port/unit. Set up your Account Key under <b>Advanced</b> below.
|
|
44
40
|
</div>
|
|
41
|
+
|
|
45
42
|
<div class="form-row">
|
|
46
43
|
<label for="node-input-functionCode"><i class="fa fa-list"></i> Function Code</label>
|
|
47
44
|
<select id="node-input-functionCode">
|
|
@@ -55,7 +52,30 @@
|
|
|
55
52
|
<label for="node-input-address"><i class="fa fa-map-marker"></i> Start Address</label>
|
|
56
53
|
<input type="number" id="node-input-address" min="0" max="65535" placeholder="0">
|
|
57
54
|
</div>
|
|
58
|
-
|
|
55
|
+
|
|
56
|
+
<div class="form-row" style="margin:14px 0 4px 0;border-top:1px solid #e5e7eb;padding-top:10px">
|
|
57
|
+
<a href="#" class="modpackqt-advanced-toggle" style="font-size:12px;color:#6b7280;text-decoration:none;cursor:pointer">▸ Advanced — runtime config & manual entry</a>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="modpackqt-advanced" style="display:none">
|
|
60
|
+
<div class="form-row">
|
|
61
|
+
<label for="node-input-server"><i class="fa fa-cog"></i> Runtime</label>
|
|
62
|
+
<input type="text" id="node-input-server">
|
|
63
|
+
</div>
|
|
64
|
+
<div class="form-row">
|
|
65
|
+
<label for="node-input-targetHost"><i class="fa fa-plug"></i> Target Host</label>
|
|
66
|
+
<input type="text" id="node-input-targetHost" placeholder="Modbus device IP/hostname (TCP only)">
|
|
67
|
+
</div>
|
|
68
|
+
<div class="form-row">
|
|
69
|
+
<label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
|
|
70
|
+
<input type="number" id="node-input-targetPort" min="1" max="65535" placeholder="502">
|
|
71
|
+
</div>
|
|
72
|
+
<div class="form-row">
|
|
73
|
+
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
74
|
+
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="form-tips" style="margin-top:14px">
|
|
59
79
|
<b>Input:</b> <code>msg.payload</code> = number (FC5/FC6) or array of numbers (FC15/FC16).
|
|
60
80
|
Encode multi-register values upstream with
|
|
61
81
|
<a href="https://www.npmjs.com/package/node-red-contrib-bytes-modpackqt" target="_blank">node-red-contrib-bytes-modpackqt</a>.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
color: '#7c3aed',
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: '' },
|
|
7
|
+
server: { value: '', type: 'modpackqt-config' },
|
|
7
8
|
bindHost: { value: '0.0.0.0' },
|
|
8
9
|
bindPort: { value: 1502, required: true, validate: RED.validators.number() },
|
|
9
10
|
unitId: { value: 1, required: true, validate: RED.validators.number() }
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
paletteLabel: 'modbus slave probe',
|
|
18
19
|
oneditprepare: function () {
|
|
19
20
|
const node = this;
|
|
21
|
+
if (window.ModPackQTSlavePicker) ModPackQTSlavePicker.attach(this);
|
|
22
|
+
if (window.ModPackQTAdvancedToggle) ModPackQTAdvancedToggle.attach('modpackqt-slave-probe');
|
|
20
23
|
const buildLink = (info) => {
|
|
21
24
|
const probeHost = (info && info.host) || '127.0.0.1';
|
|
22
25
|
const probePort = (info && info.port) || 8502;
|
|
@@ -38,24 +41,86 @@
|
|
|
38
41
|
});
|
|
39
42
|
</script>
|
|
40
43
|
|
|
44
|
+
<!-- Shared slave-picker helper — loaded once per editor session -->
|
|
45
|
+
<script type="text/javascript">
|
|
46
|
+
if (!window.ModPackQTSlavePicker) {
|
|
47
|
+
window.ModPackQTSlavePicker = {
|
|
48
|
+
attach: function (node) {
|
|
49
|
+
const $sel = $('#node-input-modpackqt-slave-picker');
|
|
50
|
+
if (!$sel.length) return;
|
|
51
|
+
const reload = function () {
|
|
52
|
+
const cfgId = $('#node-input-server').val();
|
|
53
|
+
$sel.empty().append('<option value="">— Loading… —</option>');
|
|
54
|
+
if (!cfgId || cfgId === '_ADD_') {
|
|
55
|
+
$sel.empty().append('<option value="">— Set up runtime & Account Key under Advanced —</option>');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
$.getJSON('modpackqt/slaves?config=' + encodeURIComponent(cfgId))
|
|
59
|
+
.done(function (rows) {
|
|
60
|
+
$sel.empty().append('<option value="">— Manual entry (see Advanced) —</option>');
|
|
61
|
+
(rows || [])
|
|
62
|
+
.filter(function (s) { return s && (s.protocol === 'tcp' || !s.protocol); })
|
|
63
|
+
.forEach(function (s) {
|
|
64
|
+
const label = (s.name || '(unnamed)') + ' — :' + (s.port || 1502) + ' #' + (s.unitId || 1);
|
|
65
|
+
$('<option>').val(s.id).text(label).data('row', s).appendTo($sel);
|
|
66
|
+
});
|
|
67
|
+
})
|
|
68
|
+
.fail(function (xhr) {
|
|
69
|
+
const msg = (xhr.responseJSON && xhr.responseJSON.error) || ('HTTP ' + xhr.status);
|
|
70
|
+
$sel.empty().append($('<option>').val('').text('— ' + msg + ' —'));
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
$('#node-input-server').on('change', reload);
|
|
74
|
+
$('#modpackqt-slave-picker-refresh').on('click', function (e) { e.preventDefault(); reload(); });
|
|
75
|
+
$sel.on('change', function () {
|
|
76
|
+
const row = $(this).find('option:selected').data('row');
|
|
77
|
+
if (!row) return;
|
|
78
|
+
$('#node-input-bindPort').val(row.port || 1502);
|
|
79
|
+
$('#node-input-unitId').val(row.unitId || 1);
|
|
80
|
+
});
|
|
81
|
+
reload();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
41
87
|
<script type="text/html" data-template-name="modpackqt-slave-probe">
|
|
42
88
|
<div class="form-row">
|
|
43
89
|
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
44
90
|
<input type="text" id="node-input-name" placeholder="(optional, e.g. Fake Inverter)">
|
|
45
91
|
</div>
|
|
46
92
|
<div class="form-row">
|
|
47
|
-
<label for="node-input-
|
|
48
|
-
<
|
|
93
|
+
<label for="node-input-modpackqt-slave-picker"><i class="fa fa-cloud-download"></i> My Slaves</label>
|
|
94
|
+
<select id="node-input-modpackqt-slave-picker" style="width:65%"></select>
|
|
95
|
+
<button type="button" id="modpackqt-slave-picker-refresh" class="red-ui-button" title="Reload from modpackqt.com" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
|
|
49
96
|
</div>
|
|
50
|
-
<div class="form-
|
|
51
|
-
|
|
52
|
-
<input type="number" id="node-input-bindPort" min="1" max="65535" placeholder="1502">
|
|
97
|
+
<div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
|
|
98
|
+
Pick a saved slave config from your modpackqt.com account — auto-fills port/unit. Set up your Account Key under <b>Advanced</b> below.
|
|
53
99
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
100
|
+
|
|
101
|
+
<div class="form-row" style="margin:14px 0 4px 0;border-top:1px solid #e5e7eb;padding-top:10px">
|
|
102
|
+
<a href="#" class="modpackqt-advanced-toggle" style="font-size:12px;color:#6b7280;text-decoration:none;cursor:pointer">▸ Advanced — runtime config & manual entry</a>
|
|
57
103
|
</div>
|
|
58
|
-
<div class="
|
|
104
|
+
<div class="modpackqt-advanced" style="display:none">
|
|
105
|
+
<div class="form-row">
|
|
106
|
+
<label for="node-input-server"><i class="fa fa-cog"></i> Runtime</label>
|
|
107
|
+
<input type="text" id="node-input-server">
|
|
108
|
+
</div>
|
|
109
|
+
<div class="form-row">
|
|
110
|
+
<label for="node-input-bindHost"><i class="fa fa-globe"></i> Bind Host</label>
|
|
111
|
+
<input type="text" id="node-input-bindHost" placeholder="0.0.0.0 (all interfaces)">
|
|
112
|
+
</div>
|
|
113
|
+
<div class="form-row">
|
|
114
|
+
<label for="node-input-bindPort"><i class="fa fa-hashtag"></i> Listen Port</label>
|
|
115
|
+
<input type="number" id="node-input-bindPort" min="1" max="65535" placeholder="1502">
|
|
116
|
+
</div>
|
|
117
|
+
<div class="form-row">
|
|
118
|
+
<label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
|
|
119
|
+
<input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="form-tips" style="margin-top:14px">
|
|
59
124
|
<b>One probe = one fake slave.</b> For multiple slaves, drop one node per
|
|
60
125
|
port. The web console aggregates all slave probes into a unified register
|
|
61
126
|
editor — switch between them with one click.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-modbus-modpackqt",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Modbus commissioning, testing & analysis tools for Node-RED. Embedded Modbus TCP/RTU master + slave server, FC1/FC2/FC3/FC4 reads, FC5/FC6/FC15/FC16 writes, built-in slave register store, and a passive traffic monitor for debugging. 100% free, MIT, no usage limits. By ModPackQT — open the matching web console at modpackqt.com for register decoding, simulation and AI assistance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|