node-red-contrib-modbus-modpackqt 3.3.3 → 3.3.6

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,40 @@ 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.3.6] — 2026-05-10
8
+
9
+ ### Added
10
+
11
+ - **Account Key + My Connections picker on the runtime config node.**
12
+ Paste your ModPackQT tunnel key into the config dialog — the
13
+ **My Connections** dropdown loads your saved devices from
14
+ modpackqt.com and auto-fills Host, Port, and Unit ID. Manual entry
15
+ still works as a fallback.
16
+ - Config node credentials are now registered so the admin proxy
17
+ (`/modpackqt/connections`) can look up the key without exposing it
18
+ to the browser.
19
+
20
+ ## [3.3.5] — 2026-05-10
21
+
22
+ ### Changed
23
+
24
+ - **Port and Unit ID fields removed from slave server dialog.** They are now
25
+ hidden — the only way to set them is by picking from the **My Slaves**
26
+ profile picker. This enforces the cloud-driven config workflow and removes
27
+ manual entry that could conflict with the saved profile.
28
+
29
+ ## [3.3.4] — 2026-05-10
30
+
31
+ ### Changed
32
+
33
+ - **`modpackqt-slave-server` is now a visible palette node** (was a hidden
34
+ config node). Drop it on the canvas like any other node — it shows up in
35
+ the ModPackQT section of the palette as **modbus slave server**.
36
+ - **`slave-read` and `slave-write`** now have a **Slave Server** dropdown
37
+ that lists all `modbus slave server` nodes on the canvas. Select one to
38
+ point at its register store.
39
+ - **`modpackqt-slave-probe` removed** from the palette (old node type gone).
40
+
7
41
  ## [3.3.3] — 2026-05-10
8
42
 
9
43
  ### Added
@@ -18,7 +18,7 @@
18
18
  const http = require('http');
19
19
  const { URL } = require('url');
20
20
 
21
- const PALETTE_VERSION = '3.3.3';
21
+ const PALETTE_VERSION = '3.3.6';
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;
@@ -1,6 +1,9 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('modpackqt-config', {
3
3
  category: 'config',
4
+ credentials: {
5
+ apiKey: { type: 'password' }
6
+ },
4
7
  defaults: {
5
8
  name: { value: 'ModPackQT Device' },
6
9
  targetHost: { value: 'localhost' },
@@ -39,6 +42,49 @@
39
42
  toggleRtu();
40
43
  toggleSlave();
41
44
 
45
+ // ── My Connections picker
46
+ const nodeId = this.id;
47
+ const $sel = $('#node-config-input-modpackqt-conn-picker');
48
+ const loadConnections = function () {
49
+ $sel.empty().append('<option value="">— Loading… —</option>');
50
+ if (!nodeId) {
51
+ $sel.empty().append('<option value="">— Save this config first, then reopen —</option>');
52
+ return;
53
+ }
54
+ $.getJSON('modpackqt/connections?probe=' + encodeURIComponent(nodeId))
55
+ .done(function (rows) {
56
+ $sel.empty().append('<option value="">— Manual entry —</option>');
57
+ (rows || []).forEach(function (c) {
58
+ const label = (c.name || '(unnamed)') + (c.connectionType === 'rtu'
59
+ ? ' — RTU'
60
+ : ' — ' + (c.host || '?') + ':' + (c.port || 502) + ' #' + (c.unitId || 1));
61
+ $('<option>').val(c.id).text(label).data('conn', c).appendTo($sel);
62
+ });
63
+ })
64
+ .fail(function (xhr) {
65
+ const msg = (xhr.responseJSON && xhr.responseJSON.error) || ('HTTP ' + xhr.status);
66
+ $sel.empty().append($('<option>').val('').text('— ' + msg + ' —'));
67
+ });
68
+ };
69
+ $('#modpackqt-conn-picker-refresh').on('click', function (e) {
70
+ e.preventDefault();
71
+ loadConnections();
72
+ });
73
+ $sel.on('change', function () {
74
+ const c = $(this).find('option:selected').data('conn');
75
+ if (!c) return;
76
+ if (!$('#node-config-input-name').val() || $('#node-config-input-name').val() === 'ModPackQT Device') {
77
+ $('#node-config-input-name').val(c.name || '');
78
+ }
79
+ if (c.host) $('#node-config-input-targetHost').val(c.host);
80
+ if (c.port) $('#node-config-input-targetPort').val(c.port);
81
+ if (c.unitId) $('#node-config-input-unitId').val(c.unitId);
82
+ if (c.connectionType === 'rtu') {
83
+ $('#node-config-input-masterMode').val('rtu').trigger('change');
84
+ }
85
+ });
86
+ loadConnections();
87
+
42
88
  // ── Embedded slave server section (collapsed by default)
43
89
  const $slaveSection = $('.modpackqt-slave-section');
44
90
  const $slaveToggle = $('.modpackqt-slave-section-toggle');
@@ -63,6 +109,24 @@
63
109
  <input type="text" id="node-config-input-name" placeholder="e.g. Inverter A">
64
110
  </div>
65
111
 
112
+ <div class="form-row">
113
+ <label for="node-config-input-apiKey"><i class="fa fa-key"></i> Account Key</label>
114
+ <input type="password" id="node-config-input-apiKey" placeholder="Paste your ModPackQT tunnel key">
115
+ </div>
116
+ <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 4px 105px">
117
+ Generate at <a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
118
+ Loads your saved connections in the picker below.
119
+ </div>
120
+
121
+ <div class="form-row">
122
+ <label for="node-config-input-modpackqt-conn-picker"><i class="fa fa-cloud-download"></i> My Connections</label>
123
+ <select id="node-config-input-modpackqt-conn-picker" style="width:65%"></select>
124
+ <button type="button" id="modpackqt-conn-picker-refresh" class="red-ui-button" title="Reload" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
125
+ </div>
126
+ <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
127
+ Picks a saved connection from modpackqt.com — auto-fills Host, Port, and Unit ID below.
128
+ </div>
129
+
66
130
  <h4 style="margin-top:18px">Device</h4>
67
131
  <div class="form-row modpackqt-tcp-only">
68
132
  <label for="node-config-input-targetHost"><i class="fa fa-plug"></i> Host</label>
@@ -335,5 +335,7 @@ module.exports = function (RED) {
335
335
  });
336
336
  }
337
337
 
338
- RED.nodes.registerType('modpackqt-config', ModPackQTConfigNode);
338
+ RED.nodes.registerType('modpackqt-config', ModPackQTConfigNode, {
339
+ credentials: { apiKey: { type: 'password' } }
340
+ });
339
341
  };
@@ -3,13 +3,12 @@
3
3
  category: 'ModPackQT',
4
4
  color: '#15803d',
5
5
  defaults: {
6
- name: { value: '' },
7
- slaveServer: { value: '', type: 'modpackqt-slave-server' },
8
- server: { value: '' }, // legacy modpackqt-config with embedded slave
9
- registerType: { value: 'holding', required: true },
10
- address: { value: 0, required: true, validate: RED.validators.number() },
11
- quantity: { value: 1, required: true, validate: RED.validators.number() },
12
- pollInterval: { value: 0, validate: RED.validators.number() }
6
+ name: { value: '' },
7
+ slaveServerId: { value: '' }, // ID of a modpackqt-slave-server node on the canvas
8
+ registerType: { value: 'holding', required: true },
9
+ address: { value: 0, required: true, validate: RED.validators.number() },
10
+ quantity: { value: 1, required: true, validate: RED.validators.number() },
11
+ pollInterval: { value: 0, validate: RED.validators.number() }
13
12
  },
14
13
  inputs: 1,
15
14
  outputs: 1,
@@ -17,7 +16,17 @@
17
16
  label: function () {
18
17
  return this.name || ('Slave Read ' + (this.registerType || 'holding') + ' @' + this.address);
19
18
  },
20
- paletteLabel: 'modbus slave read'
19
+ paletteLabel: 'modbus slave read',
20
+ oneditprepare: function () {
21
+ const $sel = $('#node-input-slaveServerId');
22
+ $sel.empty().append('<option value="">— select a slave server node —</option>');
23
+ RED.nodes.eachNode(function (n) {
24
+ if (n.type === 'modpackqt-slave-server') {
25
+ const label = (n.name || (':' + n.bindPort + ' #' + n.unitId));
26
+ $('<option>').val(n.id).text(label).appendTo($sel);
27
+ }
28
+ });
29
+ }
21
30
  });
22
31
  </script>
23
32
 
@@ -27,8 +36,11 @@
27
36
  <input type="text" id="node-input-name" placeholder="Name">
28
37
  </div>
29
38
  <div class="form-row">
30
- <label for="node-input-slaveServer"><i class="fa fa-server"></i> Slave Server</label>
31
- <input type="text" id="node-input-slaveServer">
39
+ <label for="node-input-slaveServerId"><i class="fa fa-server"></i> Slave Server</label>
40
+ <select id="node-input-slaveServerId" style="width:70%"></select>
41
+ </div>
42
+ <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
43
+ Pick a <b>modbus slave server</b> node from the canvas. Deploy the flow first if the list is empty.
32
44
  </div>
33
45
  <div class="form-row">
34
46
  <label for="node-input-registerType"><i class="fa fa-list"></i> Register Type</label>
@@ -52,8 +64,7 @@
52
64
  <input type="number" id="node-input-pollInterval" min="0" placeholder="0 = trigger only">
53
65
  </div>
54
66
  <div class="form-tips">
55
- <b>Reads from a slave server's register store.</b> Point at a
56
- <b>modpackqt-slave-server</b> node. Override at runtime via
67
+ Reads from the slave server's register store. Override at runtime via
57
68
  <code>msg.address</code>, <code>msg.quantity</code>, or <code>msg.registerType</code>.
58
69
  </div>
59
70
  <div class="form-row" style="margin-top:18px;padding-top:14px;border-top:1px solid #e5e7eb;text-align:center;">
@@ -68,16 +79,12 @@
68
79
  </script>
69
80
 
70
81
  <script type="text/html" data-help-name="modpackqt-slave-read">
71
- <p>
72
- Reads registers from a <b>modpackqt-slave-server</b> node's local store.
73
- Useful for seeing exactly what an external Modbus master connecting to that
74
- slave port will read.
75
- </p>
82
+ <p>Reads registers from a <b>modbus slave server</b> node's local store.</p>
76
83
  <h3>Setup</h3>
77
- <p>Set up a <b>Slave Server</b> config node first (pencil icon), then point this node at it.</p>
84
+ <p>Drop a <b>modbus slave server</b> node on the canvas first, then select it here.</p>
78
85
  <h3>Outputs</h3>
79
86
  <dl class="message-properties">
80
87
  <dt>payload <span class="property-type">array</span></dt>
81
- <dd>Raw register values (integers for holding/input, booleans for coils/discrete).</dd>
88
+ <dd>Register values (integers for holding/input, booleans for coils/discrete).</dd>
82
89
  </dl>
83
90
  </script>
@@ -1,8 +1,5 @@
1
1
  /**
2
- * ModPackQT Slave Read — read from a slave server's local register store.
3
- *
4
- * Points at a modpackqt-slave-server config node (preferred) or falls back
5
- * to the legacy modpackqt-config with an embedded slave (backward compat).
2
+ * ModPackQT Slave Read — reads from a modpackqt-slave-server node's register store.
6
3
  */
7
4
  module.exports = function (RED) {
8
5
  function ModPackQTSlaveReadNode(config) {
@@ -14,29 +11,25 @@ module.exports = function (RED) {
14
11
  node.quantity = parseInt(config.quantity, 10) || 1;
15
12
  node.pollInterval = parseInt(config.pollInterval, 10) || 0;
16
13
 
17
- // Prefer new slave-server config node; fall back to legacy modpackqt-config.
18
- const slave = RED.nodes.getNode(config.slaveServer) || RED.nodes.getNode(config.server);
14
+ function getSlave() {
15
+ return RED.nodes.getNode(config.slaveServerId);
16
+ }
19
17
 
20
18
  let timer = null;
21
19
 
22
20
  function doRead(msg) {
23
- if (!slave) {
24
- node.status({ fill: 'red', shape: 'ring', text: 'no slave server configured' });
25
- node.error('No Slave Server configured add a modpackqt-slave-server node.', msg);
26
- return;
27
- }
28
- if (!slave.slaveEnabled) {
29
- node.status({ fill: 'red', shape: 'ring', text: 'slave server not running' });
30
- node.error('Slave server is not running — check the slave-server config node.', msg);
21
+ const slave = getSlave();
22
+ if (!slave || !slave.slaveEnabled) {
23
+ node.status({ fill: 'red', shape: 'ring', text: 'no slave server' });
24
+ node.error('No slave server selected or it is not running.', msg);
31
25
  return;
32
26
  }
33
27
  try {
34
28
  const registerType = (msg && msg.registerType) || node.registerType;
35
29
  const address = (msg && msg.address !== undefined) ? parseInt(msg.address, 10) : node.address;
36
30
  const quantity = (msg && msg.quantity !== undefined) ? parseInt(msg.quantity, 10) : node.quantity;
37
-
38
- const values = slave.slaveGet(registerType, address, quantity);
39
- const now = new Date().toLocaleTimeString();
31
+ const values = slave.slaveGet(registerType, address, quantity);
32
+ const now = new Date().toLocaleTimeString();
40
33
  node.status({ fill: 'green', shape: 'dot', text: `${registerType} @${address} [${values.length}] · ${now}` });
41
34
  node.send({
42
35
  payload: values,
@@ -1,6 +1,7 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('modpackqt-slave-server', {
3
- category: 'config',
3
+ category: 'ModPackQT',
4
+ color: '#7c3aed',
4
5
  defaults: {
5
6
  name: { value: '' },
6
7
  bindHost: { value: '0.0.0.0' },
@@ -10,16 +11,18 @@
10
11
  credentials: {
11
12
  apiKey: { type: 'password' }
12
13
  },
14
+ inputs: 0,
15
+ outputs: 0,
16
+ icon: 'font-awesome/fa-server',
13
17
  label: function () {
14
18
  return this.name || `:${this.bindPort} #${this.unitId}`;
15
19
  },
20
+ paletteLabel: 'modbus slave server',
16
21
  oneditprepare: function () {
17
22
  const node = this;
18
23
 
19
- // ── My Slaves picker — loads saved slave configs from modpackqt.com.
20
- // Pre-fills port and unit ID. Register layout / limits are managed
21
- // from the web console, not here.
22
- const $sel = $('#node-config-modpackqt-slave-picker');
24
+ // ── My Slaves picker
25
+ const $sel = $('#node-input-modpackqt-slave-picker');
23
26
  const reload = function () {
24
27
  $sel.empty().append('<option value="">— Loading… —</option>');
25
28
  if (!node.id) {
@@ -41,15 +44,13 @@
41
44
  $sel.empty().append($('<option>').val('').text('— ' + msg + ' —'));
42
45
  });
43
46
  };
44
- $('#modpackqt-slave-server-picker-refresh').on('click', function (e) {
45
- e.preventDefault(); reload();
46
- });
47
+ $('#modpackqt-slave-server-picker-refresh').on('click', function (e) { e.preventDefault(); reload(); });
47
48
  $sel.on('change', function () {
48
49
  const row = $(this).find('option:selected').data('row');
49
50
  if (!row) return;
50
- if (!node.name || node.name === '') $('#node-config-input-name').val(row.name || '');
51
- $('#node-config-input-bindPort').val(row.port || 1502);
52
- $('#node-config-input-unitId').val(row.unitId || 1);
51
+ if (!node.name) $('#node-input-name').val(row.name || '');
52
+ $('#node-input-bindPort').val(row.port || 1502);
53
+ $('#node-input-unitId').val(row.unitId || 1);
53
54
  buildLink();
54
55
  });
55
56
  reload();
@@ -57,15 +58,14 @@
57
58
  // ── Probe deep-link
58
59
  const buildLink = function () {
59
60
  $.getJSON('modpackqt-probe/info')
60
- .done(function (info) { renderLink(info); })
61
- .fail(function () { renderLink({}); });
61
+ .done(renderLink).fail(function () { renderLink({}); });
62
62
  };
63
63
  const renderLink = function (info) {
64
64
  const probeHost = (info && info.host) || '127.0.0.1';
65
65
  const probePort = (info && info.port) || 8502;
66
- const bindPort = $('#node-config-input-bindPort').val() || node.bindPort || 1502;
67
- const unitId = $('#node-config-input-unitId').val() || node.unitId || 1;
68
- const name = $('#node-config-input-name').val() || node.name || '';
66
+ const bindPort = $('#node-input-bindPort').val() || node.bindPort || 1502;
67
+ const unitId = $('#node-input-unitId').val() || node.unitId || 1;
68
+ const name = $('#node-input-name').val() || node.name || '';
69
69
  const url = 'https://modpackqt.com/slave'
70
70
  + '?probe=' + encodeURIComponent(node.id || '')
71
71
  + '&probeHost=' + encodeURIComponent(probeHost)
@@ -78,20 +78,20 @@
78
78
  $('#modpackqt-slave-server-target').text(':' + bindPort + ' · unit ' + unitId);
79
79
  };
80
80
  buildLink();
81
- $('#node-config-input-bindPort, #node-config-input-unitId').on('change input', buildLink);
81
+ $('#node-input-bindPort, #node-input-unitId').on('change input', buildLink);
82
82
  }
83
83
  });
84
84
  </script>
85
85
 
86
86
  <script type="text/html" data-template-name="modpackqt-slave-server">
87
87
  <div class="form-row">
88
- <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
89
- <input type="text" id="node-config-input-name" placeholder="e.g. Fake Inverter">
88
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
89
+ <input type="text" id="node-input-name" placeholder="e.g. Fake Inverter">
90
90
  </div>
91
91
 
92
92
  <div class="form-row">
93
- <label for="node-config-input-apiKey"><i class="fa fa-key"></i> Account Key</label>
94
- <input type="password" id="node-config-input-apiKey" placeholder="Paste your ModPackQT tunnel key">
93
+ <label for="node-input-apiKey"><i class="fa fa-key"></i> Account Key</label>
94
+ <input type="password" id="node-input-apiKey" placeholder="Paste your ModPackQT tunnel key">
95
95
  </div>
96
96
  <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
97
97
  Generate at <a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
@@ -99,25 +99,18 @@
99
99
  </div>
100
100
 
101
101
  <div class="form-row">
102
- <label for="node-config-modpackqt-slave-picker"><i class="fa fa-cloud-download"></i> My Slaves</label>
103
- <select id="node-config-modpackqt-slave-picker" style="width:65%"></select>
104
- <button type="button" id="modpackqt-slave-server-picker-refresh" class="red-ui-button" title="Reload from modpackqt.com" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
102
+ <label for="node-input-modpackqt-slave-picker"><i class="fa fa-cloud-download"></i> My Slaves</label>
103
+ <select id="node-input-modpackqt-slave-picker" style="width:65%"></select>
104
+ <button type="button" id="modpackqt-slave-server-picker-refresh" class="red-ui-button" title="Reload" style="margin-left:4px"><i class="fa fa-refresh"></i></button>
105
105
  </div>
106
106
  <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
107
- Pick a saved slave from modpackqt.com auto-fills port and unit below.
107
+ Required — pick a saved slave to set the port and unit ID.
108
108
  Register layout and limits are managed from the web console.
109
109
  </div>
110
110
 
111
- <div class="form-row">
112
- <label for="node-config-input-bindPort"><i class="fa fa-hashtag"></i> Port</label>
113
- <input type="number" id="node-config-input-bindPort" min="1024" max="65535" placeholder="1502">
114
- </div>
115
- <div class="form-row">
116
- <label for="node-config-input-unitId"><i class="fa fa-id-card"></i> Unit ID</label>
117
- <input type="number" id="node-config-input-unitId" min="1" max="247" placeholder="1">
118
- </div>
119
-
120
- <input type="hidden" id="node-config-input-bindHost">
111
+ <input type="hidden" id="node-input-bindHost">
112
+ <input type="hidden" id="node-input-bindPort">
113
+ <input type="hidden" id="node-input-unitId">
121
114
 
122
115
  <div class="form-row" style="margin-top:18px;padding-top:14px;border-top:1px solid #e5e7eb;text-align:center;">
123
116
  <a id="modpackqt-slave-server-link" href="https://modpackqt.com" target="_blank" rel="noopener noreferrer"
@@ -134,27 +127,20 @@
134
127
 
135
128
  <script type="text/html" data-help-name="modpackqt-slave-server">
136
129
  <p>
137
- Runs a Modbus TCP slave server and owns the register store. Point
138
- <b>slave-read</b> and <b>slave-write</b> nodes at it to read and write
139
- registers from your flow. External Modbus masters can also connect directly
140
- to the configured port and read whatever your flow has written.
130
+ Runs a Modbus TCP slave server with its own full register store.
131
+ Drop one on the canvas per fake device. <b>Slave-read</b> and
132
+ <b>slave-write</b> nodes target it by node ID to read and write
133
+ registers from the flow.
141
134
  </p>
142
-
143
- <h3>How to set it up</h3>
135
+ <h3>Setup</h3>
144
136
  <ol>
145
- <li>Drop a <b>slave-read</b> or <b>slave-write</b> node and click its pencil icon next to "Slave Server" to create a new slave server.</li>
146
- <li>Paste your Account Key and pick a saved slave from <b>My Slaves</b> — it pre-fills the port and unit ID.</li>
147
- <li>The register layout and coil/register counts are managed from the modpackqt.com web console — click <b>Open in ModPackQT Console</b> to edit them.</li>
137
+ <li>Drop this node, set Port and Unit ID (or pick from <b>My Slaves</b>).</li>
138
+ <li>On your slave-read / slave-write nodes, select this node in the <b>Slave Server</b> dropdown.</li>
139
+ <li>Click <b>Open in ModPackQT Console</b> to manage the register layout from the web.</li>
148
140
  </ol>
149
-
150
141
  <h3>Account Key</h3>
151
- <p>Generate one at <a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
152
- Used to load your saved slave configs and to let the web console connect to this server.</p>
153
-
154
- <h3>Multiple slave servers</h3>
155
- <p>Create one slave-server config per port/unit combination. Slave-read and
156
- slave-write nodes each pick which server to talk to.</p>
157
-
142
+ <p>Generate at <a href="https://modpackqt.com/settings" target="_blank" rel="noopener">modpackqt.com → Settings → Cloud Gateways</a>.
143
+ Lets the web console connect and loads your saved slave configs.</p>
158
144
  <h3>Port note</h3>
159
- <p>Use ports above 1024 (e.g. 1502, 1503) to avoid needing root privileges.</p>
145
+ <p>Use ports above 1024 (e.g. 1502, 1503) to avoid needing root privileges. Each server must use a unique port.</p>
160
146
  </script>
@@ -1,17 +1,16 @@
1
1
  /**
2
2
  * ModPackQT Slave Server — Modbus TCP slave with a full register store.
3
3
  *
4
- * This is a config node. Drop one per fake device. Each server:
4
+ * Drop one on the canvas per fake device. Each server:
5
5
  * - Binds its own TCP port and listens for external Modbus masters
6
6
  * - Owns 65 536 registers per type (coils, discrete, holding, input)
7
- * - Exposes slaveGet() / slaveSet() so slave-read / slave-write can
8
- * push and pull values from the flow side
9
- * - Registers with the probe runtime so the modpackqt.com web console
10
- * can open a live register editor for it
7
+ * - Exposes slaveGet() / slaveSet() so slave-read / slave-write nodes
8
+ * can push and pull values from the flow side
9
+ * - Registers with the probe runtime so modpackqt.com can open a live
10
+ * register editor for it
11
11
  *
12
12
  * Register limits and layout are managed from the modpackqt.com web
13
- * console (loaded via the My Slaves picker). Node-RED only needs the
14
- * port and unit ID to bind the TCP listener.
13
+ * console this node only needs port and unit ID to bind.
15
14
  */
16
15
  module.exports = function (RED) {
17
16
  const ModbusRTU = require('modbus-serial');
@@ -26,9 +25,7 @@ module.exports = function (RED) {
26
25
  node.bindPort = parseInt(config.bindPort, 10) || 1502;
27
26
  node.unitId = parseInt(config.unitId, 10) || 1;
28
27
 
29
- // ── Register store — full Modbus address space per type.
30
- // Register limits / layout are defined by the modpackqt.com slave
31
- // config and are managed via the web console, not here.
28
+ // ── Register store
32
29
  const store = {
33
30
  coils: new Array(65536).fill(false),
34
31
  discrete: new Array(65536).fill(false),
@@ -36,7 +33,7 @@ module.exports = function (RED) {
36
33
  input: new Array(65536).fill(0)
37
34
  };
38
35
 
39
- // ── Flow-side API — used by slave-read and slave-write nodes.
36
+ // ── Flow-side API — used by slave-read and slave-write nodes
40
37
  node.slaveEnabled = true;
41
38
 
42
39
  node.slaveGet = function (type, address, quantity) {
@@ -73,11 +70,13 @@ module.exports = function (RED) {
73
70
  server.on('socketError', (err) => node.warn(`[slave-server] socket: ${err.message}`));
74
71
  server.on('serverError', (err) => node.error(`[slave-server] server: ${err.message}`));
75
72
  node.log(`[modpackqt] slave server listening on ${node.bindHost}:${node.bindPort} #${node.unitId}`);
73
+ node.status({ fill: 'green', shape: 'dot', text: `listening :${node.bindPort} #${node.unitId}` });
76
74
  } catch (err) {
77
75
  node.error(`Failed to start slave server: ${err.message}`);
76
+ node.status({ fill: 'red', shape: 'ring', text: 'failed to start' });
78
77
  }
79
78
 
80
- // ── Probe runtime registration — lets the web console find this slave.
79
+ // ── Probe runtime — lets the web console find and control this server
81
80
  const probe = {
82
81
  id: node.id,
83
82
  kind: 'slave',
@@ -3,11 +3,10 @@
3
3
  category: 'ModPackQT',
4
4
  color: '#15803d',
5
5
  defaults: {
6
- name: { value: '' },
7
- slaveServer: { value: '', type: 'modpackqt-slave-server' },
8
- server: { value: '' }, // legacy modpackqt-config with embedded slave
9
- registerType: { value: 'holding', required: true },
10
- address: { value: 0, required: true, validate: RED.validators.number() }
6
+ name: { value: '' },
7
+ slaveServerId: { value: '' }, // ID of a modpackqt-slave-server node on the canvas
8
+ registerType: { value: 'holding', required: true },
9
+ address: { value: 0, required: true, validate: RED.validators.number() }
11
10
  },
12
11
  inputs: 1,
13
12
  outputs: 1,
@@ -15,7 +14,17 @@
15
14
  label: function () {
16
15
  return this.name || ('Slave Write ' + (this.registerType || 'holding') + ' @' + this.address);
17
16
  },
18
- paletteLabel: 'modbus slave write'
17
+ paletteLabel: 'modbus slave write',
18
+ oneditprepare: function () {
19
+ const $sel = $('#node-input-slaveServerId');
20
+ $sel.empty().append('<option value="">— select a slave server node —</option>');
21
+ RED.nodes.eachNode(function (n) {
22
+ if (n.type === 'modpackqt-slave-server') {
23
+ const label = (n.name || (':' + n.bindPort + ' #' + n.unitId));
24
+ $('<option>').val(n.id).text(label).appendTo($sel);
25
+ }
26
+ });
27
+ }
19
28
  });
20
29
  </script>
21
30
 
@@ -25,8 +34,11 @@
25
34
  <input type="text" id="node-input-name" placeholder="Name">
26
35
  </div>
27
36
  <div class="form-row">
28
- <label for="node-input-slaveServer"><i class="fa fa-server"></i> Slave Server</label>
29
- <input type="text" id="node-input-slaveServer">
37
+ <label for="node-input-slaveServerId"><i class="fa fa-server"></i> Slave Server</label>
38
+ <select id="node-input-slaveServerId" style="width:70%"></select>
39
+ </div>
40
+ <div class="form-tips" style="font-size:11px;color:#6b7280;margin:-8px 0 12px 105px">
41
+ Pick a <b>modbus slave server</b> node from the canvas. Deploy the flow first if the list is empty.
30
42
  </div>
31
43
  <div class="form-row">
32
44
  <label for="node-input-registerType"><i class="fa fa-list"></i> Register Type</label>
@@ -42,8 +54,8 @@
42
54
  <input type="number" id="node-input-address" min="0" max="65535" placeholder="0">
43
55
  </div>
44
56
  <div class="form-tips">
45
- <b>Pushes values into a slave server's register store.</b> Any external Modbus master
46
- connecting to that slave port will read whatever you last wrote.
57
+ Pushes values into the slave server's register store. External Modbus masters
58
+ connecting to that port will read whatever you last wrote.
47
59
  Send <code>msg.payload</code> as a number, array, or JSON string.
48
60
  </div>
49
61
  <div class="form-row" style="margin-top:18px;padding-top:14px;border-top:1px solid #e5e7eb;text-align:center;">
@@ -58,13 +70,10 @@
58
70
  </script>
59
71
 
60
72
  <script type="text/html" data-help-name="modpackqt-slave-write">
61
- <p>
62
- Writes values into a <b>modpackqt-slave-server</b> node's register store.
63
- External Modbus masters connecting to that slave's port will read whatever
64
- was last written here.
65
- </p>
73
+ <p>Writes values into a <b>modbus slave server</b> node's register store.
74
+ External Modbus masters connecting to that slave's port will read whatever was last written.</p>
66
75
  <h3>Setup</h3>
67
- <p>Set up a <b>Slave Server</b> config node first (pencil icon), then point this node at it.</p>
76
+ <p>Drop a <b>modbus slave server</b> node on the canvas first, then select it here.</p>
68
77
  <h3>Input</h3>
69
78
  <dl class="message-properties">
70
79
  <dt>payload <span class="property-type">number | array | string</span></dt>
@@ -1,10 +1,6 @@
1
1
  /**
2
- * ModPackQT Slave Write — write into a slave server's local register store.
3
- *
4
- * Points at a modpackqt-slave-server config node (preferred) or falls back
5
- * to the legacy modpackqt-config with an embedded slave (backward compat).
6
- * External Modbus masters connecting to the slave port will read whatever
7
- * was last written here.
2
+ * ModPackQT Slave Write — writes into a modpackqt-slave-server node's register store.
3
+ * External Modbus masters connecting to the slave port will read whatever was last written.
8
4
  */
9
5
  module.exports = function (RED) {
10
6
  function ModPackQTSlaveWriteNode(config) {
@@ -14,18 +10,15 @@ module.exports = function (RED) {
14
10
  node.registerType = config.registerType || 'holding';
15
11
  node.address = parseInt(config.address, 10) || 0;
16
12
 
17
- // Prefer new slave-server config node; fall back to legacy modpackqt-config.
18
- const slave = RED.nodes.getNode(config.slaveServer) || RED.nodes.getNode(config.server);
13
+ function getSlave() {
14
+ return RED.nodes.getNode(config.slaveServerId);
15
+ }
19
16
 
20
17
  node.on('input', function (msg) {
21
- if (!slave) {
22
- node.status({ fill: 'red', shape: 'ring', text: 'no slave server configured' });
23
- node.error('No Slave Server configured add a modpackqt-slave-server node.', msg);
24
- return;
25
- }
26
- if (!slave.slaveEnabled) {
27
- node.status({ fill: 'red', shape: 'ring', text: 'slave server not running' });
28
- node.error('Slave server is not running — check the slave-server config node.', msg);
18
+ const slave = getSlave();
19
+ if (!slave || !slave.slaveEnabled) {
20
+ node.status({ fill: 'red', shape: 'ring', text: 'no slave server' });
21
+ node.error('No slave server selected or it is not running.', msg);
29
22
  return;
30
23
  }
31
24
  try {
@@ -42,7 +35,6 @@ module.exports = function (RED) {
42
35
  throw new Error(`Invalid payload — expected number or array, got: ${JSON.stringify(msg.payload)}`);
43
36
  }
44
37
  const address = msg.address !== undefined ? parseInt(msg.address, 10) : node.address;
45
-
46
38
  slave.slaveSet(registerType, address, values);
47
39
  const now = new Date().toLocaleTimeString();
48
40
  node.status({ fill: 'green', shape: 'dot', text: `wrote ${values.length} → ${registerType} @${address} · ${now}` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-modbus-modpackqt",
3
- "version": "3.3.3",
3
+ "version": "3.3.6",
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",
@@ -51,7 +51,6 @@
51
51
  "modpackqt-slave-write": "nodes/modpackqt-slave-write.js",
52
52
  "modpackqt-traffic": "nodes/modpackqt-traffic.js",
53
53
  "modpackqt-master-probe": "nodes/modpackqt-master-probe.js",
54
- "modpackqt-slave-probe": "nodes/modpackqt-slave-probe.js",
55
54
  "modpackqt-slave-server": "nodes/modpackqt-slave-server.js"
56
55
  }
57
56
  },