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 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.1.1';
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">API Key (optional — reserved for future cloud features)</h4>
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> API Key</label>
108
- <input type="password" id="node-config-input-apiKey" placeholder="Optional — leave blank for local-only use">
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 &amp; 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>API key (optional)</h3>
151
+ <h3>ModPackQT Account Key (optional)</h3>
147
152
  <p>
148
- Reserved for future cloud features (profile sync, remote console). Leave blank
149
- for local-only use all Modbus functionality works without a key.
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 &amp; 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-targetHost"><i class="fa fa-plug"></i> Target Host</label>
51
- <input type="text" id="node-input-targetHost" placeholder="192.168.1.10">
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-row">
54
- <label for="node-input-targetPort"><i class="fa fa-hashtag"></i> Target Port</label>
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
- <div class="form-row">
58
- <label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
59
- <input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
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 &amp; manual entry</a>
60
63
  </div>
61
- <div class="form-row">
62
- <label for="node-input-timeoutMs"><i class="fa fa-clock-o"></i> Timeout (ms)</label>
63
- <input type="number" id="node-input-timeoutMs" placeholder="3000">
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
- <div class="form-tips">
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 &amp; 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 &amp; 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-server"><i class="fa fa-cog"></i> Runtime</label>
33
- <input type="text" id="node-input-server">
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-row">
36
- <label for="node-input-targetHost"><i class="fa fa-plug"></i> Target Host</label>
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
- <div class="form-tips">
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 &amp; 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-server"><i class="fa fa-cog"></i> Runtime</label>
31
- <input type="text" id="node-input-server">
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-row">
34
- <label for="node-input-targetHost"><i class="fa fa-plug"></i> Target Host</label>
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
- <div class="form-tips">
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 &amp; 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 &amp; 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-bindHost"><i class="fa fa-globe"></i> Bind Host</label>
48
- <input type="text" id="node-input-bindHost" placeholder="0.0.0.0 (all interfaces)">
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-row">
51
- <label for="node-input-bindPort"><i class="fa fa-hashtag"></i> Listen Port</label>
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
- <div class="form-row">
55
- <label for="node-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
56
- <input type="number" id="node-input-unitId" min="1" max="247" placeholder="1">
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 &amp; manual entry</a>
57
103
  </div>
58
- <div class="form-tips">
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.1.1",
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",