node-red-modbus-dynamic-server 0.1.0
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/LICENSE +19 -0
- package/README.md +180 -0
- package/package.json +75 -0
- package/src/modbus-dynamic-proxy.html +108 -0
- package/src/modbus-dynamic-proxy.js +400 -0
- package/src/modbus-dynamic-server-config.html +85 -0
- package/src/modbus-dynamic-server-config.js +391 -0
- package/src/modbus-dynamic-server-response.html +88 -0
- package/src/modbus-dynamic-server-response.js +160 -0
- package/src/modbus-dynamic-server.html +90 -0
- package/src/modbus-dynamic-server.js +45 -0
- package/src/modbus-fc-filter.html +205 -0
- package/src/modbus-fc-filter.js +85 -0
- package/src/modbus-proxy-target.html +175 -0
- package/src/modbus-proxy-target.js +302 -0
- package/src/modbus-registers-config.html +109 -0
- package/src/modbus-registers-config.js +216 -0
- package/src/modbus-registers-read.html +133 -0
- package/src/modbus-registers-read.js +204 -0
- package/src/modbus-registers-respond.html +116 -0
- package/src/modbus-registers-respond.js +286 -0
- package/src/modbus-registers-write.html +157 -0
- package/src/modbus-registers-write.js +156 -0
- package/src/modbus-response-context.js +52 -0
- package/src/modbus-server-exception.html +82 -0
- package/src/modbus-server-exception.js +44 -0
- package/src/modbus-source-router.html +296 -0
- package/src/modbus-source-router.js +120 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="modbus-registers-read">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-registerMap"><i class="fa fa-database"></i> Register Map</label>
|
|
9
|
+
<input type="text" id="node-input-registerMap">
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="form-row">
|
|
13
|
+
<label for="node-input-registerType"><i class="fa fa-filter"></i> Register Type</label>
|
|
14
|
+
<select id="node-input-registerType">
|
|
15
|
+
<option value="holding">holding</option>
|
|
16
|
+
<option value="input">input</option>
|
|
17
|
+
<option value="coil">coil</option>
|
|
18
|
+
<option value="discrete">discrete</option>
|
|
19
|
+
</select>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="form-row">
|
|
23
|
+
<label for="node-input-registerAddress"><i class="fa fa-hashtag"></i> Register Address</label>
|
|
24
|
+
<input type="number" id="node-input-registerAddress" min="0" placeholder="0">
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="form-row">
|
|
28
|
+
<label for="node-input-registerFormat"><i class="fa fa-code"></i> Format</label>
|
|
29
|
+
<select id="node-input-registerFormat">
|
|
30
|
+
<option value="int16">int16</option>
|
|
31
|
+
<option value="uint16">uint16</option>
|
|
32
|
+
<option value="int32">int32</option>
|
|
33
|
+
<option value="uint32">uint32</option>
|
|
34
|
+
<option value="float32">float32</option>
|
|
35
|
+
</select>
|
|
36
|
+
</div>
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script type="text/html" data-help-name="modbus-registers-read">
|
|
40
|
+
<p>Reads a value from a shared <code>modbus-registers-config</code> register map on each input
|
|
41
|
+
message and emits the decoded result as a normal Node-RED message.</p>
|
|
42
|
+
|
|
43
|
+
<h3>Inputs</h3>
|
|
44
|
+
<dl class="message-properties">
|
|
45
|
+
<dt class="optional">registerType <span class="property-type">string</span></dt>
|
|
46
|
+
<dd>Overrides the configured register type for this message only.
|
|
47
|
+
Allowed values: <code>holding</code>, <code>input</code>, <code>coil</code>,
|
|
48
|
+
<code>discrete</code>.</dd>
|
|
49
|
+
|
|
50
|
+
<dt class="optional">registerAddress <span class="property-type">number</span></dt>
|
|
51
|
+
<dd>Overrides the configured register address (0-based) for this message only.</dd>
|
|
52
|
+
|
|
53
|
+
<dt class="optional">registerFormat <span class="property-type">string</span></dt>
|
|
54
|
+
<dd>Overrides the configured data format for this message only.
|
|
55
|
+
Allowed values: <code>int16</code>, <code>uint16</code>, <code>int32</code>,
|
|
56
|
+
<code>uint32</code>, <code>float32</code>.</dd>
|
|
57
|
+
</dl>
|
|
58
|
+
|
|
59
|
+
<h3>Outputs</h3>
|
|
60
|
+
<ol class="node-ports">
|
|
61
|
+
<li>Read result message
|
|
62
|
+
<dl class="message-properties">
|
|
63
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
64
|
+
<dd>Structured read result object with:
|
|
65
|
+
<ul>
|
|
66
|
+
<li><code>registerType</code> — register type read from.</li>
|
|
67
|
+
<li><code>registerAddress</code> — address read from.</li>
|
|
68
|
+
<li><code>registerFormat</code> — format used for decoding.</li>
|
|
69
|
+
<li><code>value</code> — decoded value.</li>
|
|
70
|
+
<li><code>wordsRead</code> — present for word/register reads.</li>
|
|
71
|
+
<li><code>bitsRead</code> — present for coil/discrete reads.</li>
|
|
72
|
+
</ul>
|
|
73
|
+
</dd>
|
|
74
|
+
|
|
75
|
+
<dt>modbusRegistersRead <span class="property-type">object</span></dt>
|
|
76
|
+
<dd>Read metadata mirror for easier downstream inspection.</dd>
|
|
77
|
+
</dl>
|
|
78
|
+
</li>
|
|
79
|
+
</ol>
|
|
80
|
+
|
|
81
|
+
<h3>Configuration</h3>
|
|
82
|
+
<dl class="message-properties">
|
|
83
|
+
<dt>Register Map <span class="property-type">modbus-registers-config</span></dt>
|
|
84
|
+
<dd>The shared register map this node reads from. Required.</dd>
|
|
85
|
+
|
|
86
|
+
<dt>Register Type <span class="property-type">select</span></dt>
|
|
87
|
+
<dd>The type of register to read:
|
|
88
|
+
<ul>
|
|
89
|
+
<li><code>holding</code> — holding registers.</li>
|
|
90
|
+
<li><code>input</code> — input registers.</li>
|
|
91
|
+
<li><code>coil</code> — single-bit coil registers.</li>
|
|
92
|
+
<li><code>discrete</code> — discrete input bits.</li>
|
|
93
|
+
</ul>
|
|
94
|
+
</dd>
|
|
95
|
+
|
|
96
|
+
<dt>Register Address <span class="property-type">number</span></dt>
|
|
97
|
+
<dd>Zero-based register address to read from. For 32-bit formats, both
|
|
98
|
+
<code>address</code> and <code>address+1</code> are read.</dd>
|
|
99
|
+
|
|
100
|
+
<dt>Format <span class="property-type">select</span></dt>
|
|
101
|
+
<dd>Decoding format for register reads:
|
|
102
|
+
<ul>
|
|
103
|
+
<li><code>int16</code> — signed 16-bit integer.</li>
|
|
104
|
+
<li><code>uint16</code> — unsigned 16-bit integer.</li>
|
|
105
|
+
<li><code>int32</code> — signed 32-bit integer (2 registers).</li>
|
|
106
|
+
<li><code>uint32</code> — unsigned 32-bit integer (2 registers).</li>
|
|
107
|
+
<li><code>float32</code> — IEEE 754 single-precision float (2 registers).</li>
|
|
108
|
+
</ul>
|
|
109
|
+
32-bit word/byte ordering follows the linked register map's
|
|
110
|
+
<em>32-bit Word Order</em> setting.</dd>
|
|
111
|
+
</dl>
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<script type="text/javascript">
|
|
115
|
+
RED.nodes.registerType('modbus-registers-read', {
|
|
116
|
+
category: 'modbus',
|
|
117
|
+
color: '#E9967A',
|
|
118
|
+
defaults: {
|
|
119
|
+
name: { value: '' },
|
|
120
|
+
registerMap: { value: '', type: 'modbus-registers-config', required: true },
|
|
121
|
+
registerType: { value: 'holding', required: true },
|
|
122
|
+
registerAddress: { value: 0, required: true, validate: RED.validators.number() },
|
|
123
|
+
registerFormat: { value: 'uint16', required: true }
|
|
124
|
+
},
|
|
125
|
+
inputs: 1,
|
|
126
|
+
outputs: 1,
|
|
127
|
+
icon: 'file.svg',
|
|
128
|
+
label: function () {
|
|
129
|
+
return this.name || 'Modbus Registers Read'
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
</script>
|
|
133
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
function formatStatusValue (value) {
|
|
2
|
+
if (value === null) return 'null'
|
|
3
|
+
if (value === undefined) return 'undefined'
|
|
4
|
+
|
|
5
|
+
if (typeof value === 'number') {
|
|
6
|
+
if (!Number.isFinite(value)) return String(value)
|
|
7
|
+
return String(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof value === 'bigint' || typeof value === 'boolean') {
|
|
11
|
+
return String(value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value.length > 20 ? `${value.slice(0, 17)}...` : value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return `[${value.length} items]`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof value === 'object') {
|
|
23
|
+
return '[object]'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return String(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = function (RED) {
|
|
30
|
+
function ModbusRegistersReadNode (config) {
|
|
31
|
+
RED.nodes.createNode(this, config)
|
|
32
|
+
const node = this
|
|
33
|
+
|
|
34
|
+
node.name = config.name || ''
|
|
35
|
+
node.registerMap = RED.nodes.getNode(config.registerMap)
|
|
36
|
+
node.registerType = config.registerType || 'holding'
|
|
37
|
+
node.registerAddress = normalizeAddress(config.registerAddress)
|
|
38
|
+
node.registerFormat = config.registerFormat || 'uint16'
|
|
39
|
+
|
|
40
|
+
if (!node.registerMap) {
|
|
41
|
+
node.status({ fill: 'red', shape: 'ring', text: 'no register map' })
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
node.status({ fill: 'grey', shape: 'ring', text: 'ready' })
|
|
46
|
+
|
|
47
|
+
node.on('input', function (msg, send, done) {
|
|
48
|
+
const registerType = normalizeRegisterType(msg.registerType || node.registerType)
|
|
49
|
+
const registerAddress = normalizeAddress(msg.registerAddress, node.registerAddress)
|
|
50
|
+
const registerFormat = normalizeRegisterFormat(msg.registerFormat || node.registerFormat)
|
|
51
|
+
|
|
52
|
+
if (!registerType) {
|
|
53
|
+
node.status({ fill: 'red', shape: 'ring', text: 'invalid register type' })
|
|
54
|
+
done(new Error('modbus-registers-read: invalid registerType'))
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!registerFormat) {
|
|
59
|
+
node.status({ fill: 'red', shape: 'ring', text: 'invalid format' })
|
|
60
|
+
done(new Error('modbus-registers-read: invalid registerFormat'))
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const readResult = readValue(node.registerMap, registerType, registerAddress, registerFormat)
|
|
65
|
+
if (!readResult.ok) {
|
|
66
|
+
node.status({ fill: 'red', shape: 'ring', text: 'read failed' })
|
|
67
|
+
done(new Error(`modbus-registers-read: ${readResult.error}`))
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
msg.payload = {
|
|
72
|
+
registerType,
|
|
73
|
+
registerAddress,
|
|
74
|
+
registerFormat,
|
|
75
|
+
value: readResult.value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (readResult.wordsRead) {
|
|
79
|
+
msg.payload.wordsRead = readResult.wordsRead
|
|
80
|
+
}
|
|
81
|
+
if (readResult.bitsRead) {
|
|
82
|
+
msg.payload.bitsRead = readResult.bitsRead
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
msg.modbusRegistersRead = {
|
|
86
|
+
registerType,
|
|
87
|
+
registerAddress,
|
|
88
|
+
registerFormat,
|
|
89
|
+
value: readResult.value,
|
|
90
|
+
wordsRead: readResult.wordsRead || 0,
|
|
91
|
+
bitsRead: readResult.bitsRead || 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
node.status({ fill: 'green', shape: 'dot', text: `read ${formatStatusValue(readResult.value)}` })
|
|
95
|
+
send(msg)
|
|
96
|
+
done()
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeAddress (value, fallback) {
|
|
101
|
+
const parsed = Number(value)
|
|
102
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
103
|
+
return fallback !== undefined ? fallback : 0
|
|
104
|
+
}
|
|
105
|
+
return Math.floor(parsed)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeRegisterType (value) {
|
|
109
|
+
if (value === 'holding' || value === 'input' || value === 'coil' || value === 'discrete') {
|
|
110
|
+
return value
|
|
111
|
+
}
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeRegisterFormat (value) {
|
|
116
|
+
if (value === 'int16' || value === 'uint16' || value === 'int32' || value === 'uint32' || value === 'float32') {
|
|
117
|
+
return value
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readValue (registerMap, registerType, registerAddress, registerFormat) {
|
|
123
|
+
if (registerType === 'coil' || registerType === 'discrete') {
|
|
124
|
+
const bits = registerMap.readBits(registerType, registerAddress, 1)
|
|
125
|
+
if (!bits) {
|
|
126
|
+
return { ok: false, error: 'address out of range' }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
value: Boolean(bits[0]),
|
|
132
|
+
bitsRead: 1
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const wordsNeeded = (registerFormat === 'int32' || registerFormat === 'uint32' || registerFormat === 'float32') ? 2 : 1
|
|
137
|
+
const words = registerMap.readWords(registerType, registerAddress, wordsNeeded)
|
|
138
|
+
if (!words) {
|
|
139
|
+
return { ok: false, error: 'address out of range' }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const value = decodeWords(words, registerFormat, registerMap.wordOrderMode)
|
|
143
|
+
if (value === null) {
|
|
144
|
+
return { ok: false, error: `unsupported format ${registerFormat}` }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
value,
|
|
150
|
+
wordsRead: wordsNeeded
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function decodeWords (words, format, wordOrderMode) {
|
|
155
|
+
const w0 = Number(words[0]) & 0xffff
|
|
156
|
+
|
|
157
|
+
if (format === 'uint16') return w0
|
|
158
|
+
if (format === 'int16') {
|
|
159
|
+
return w0 >= 0x8000 ? w0 - 0x10000 : w0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (format === 'int32' || format === 'uint32' || format === 'float32') {
|
|
163
|
+
if (!Array.isArray(words) || words.length < 2) return null
|
|
164
|
+
const w1 = Number(words[1]) & 0xffff
|
|
165
|
+
const bytes = decodeWordPairToABCDBytes(w0, w1, wordOrderMode)
|
|
166
|
+
const buf = Buffer.from(bytes)
|
|
167
|
+
|
|
168
|
+
if (format === 'uint32') return buf.readUInt32BE(0)
|
|
169
|
+
if (format === 'int32') return buf.readInt32BE(0)
|
|
170
|
+
return buf.readFloatBE(0)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function decodeWordPairToABCDBytes (firstWord, secondWord, mode) {
|
|
177
|
+
const firstHi = (firstWord >> 8) & 0xff
|
|
178
|
+
const firstLo = firstWord & 0xff
|
|
179
|
+
const secondHi = (secondWord >> 8) & 0xff
|
|
180
|
+
const secondLo = secondWord & 0xff
|
|
181
|
+
|
|
182
|
+
const normalizedMode = normalizeWordOrderMode(mode)
|
|
183
|
+
if (normalizedMode === 'ABCD') {
|
|
184
|
+
return [firstHi, firstLo, secondHi, secondLo]
|
|
185
|
+
}
|
|
186
|
+
if (normalizedMode === 'CDAB') {
|
|
187
|
+
return [secondHi, secondLo, firstHi, firstLo]
|
|
188
|
+
}
|
|
189
|
+
if (normalizedMode === 'BADC') {
|
|
190
|
+
return [firstLo, firstHi, secondLo, secondHi]
|
|
191
|
+
}
|
|
192
|
+
// DCBA
|
|
193
|
+
return [secondLo, secondHi, firstLo, firstHi]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function normalizeWordOrderMode (value) {
|
|
197
|
+
const mode = String(value || '').trim().toUpperCase()
|
|
198
|
+
if (mode === 'ABCD' || mode === 'CDAB' || mode === 'BADC' || mode === 'DCBA') return mode
|
|
199
|
+
return 'ABCD'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
RED.nodes.registerType('modbus-registers-read', ModbusRegistersReadNode)
|
|
203
|
+
}
|
|
204
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="modbus-registers-respond">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-registerMap"><i class="fa fa-database"></i> Register Map</label>
|
|
9
|
+
<input type="text" id="node-input-registerMap">
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script type="text/html" data-help-name="modbus-registers-respond">
|
|
15
|
+
<p>Handles an incoming Modbus request emitted by a <code>modbus-dynamic-server</code> node by
|
|
16
|
+
automatically reading from or writing to a shared <code>modbus-registers-config</code> register
|
|
17
|
+
map, then sending the correct Modbus response back to the client — all without manual payload
|
|
18
|
+
construction.</p>
|
|
19
|
+
|
|
20
|
+
<h3>Inputs</h3>
|
|
21
|
+
<dl class="message-properties">
|
|
22
|
+
<dt>_modbus.requestId <span class="property-type">string</span></dt>
|
|
23
|
+
<dd>Required internal request context field emitted by <code>modbus-dynamic-server</code>.</dd>
|
|
24
|
+
|
|
25
|
+
<dt>_modbus.eventName <span class="property-type">string</span></dt>
|
|
26
|
+
<dd>Required internal function-code event name from the incoming request, e.g.
|
|
27
|
+
<code>readHoldingRegisters</code>, <code>writeSingleCoil</code>.</dd>
|
|
28
|
+
|
|
29
|
+
<dt>_modbus.configNodeId <span class="property-type">string</span></dt>
|
|
30
|
+
<dd>Required internal config-node identifier used to complete the pending request.</dd>
|
|
31
|
+
|
|
32
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
33
|
+
<dd>The full jsmodbus request object as emitted by the <code>modbus-dynamic-server</code>
|
|
34
|
+
node. Must contain <code>address</code>, <code>quantity</code>, and <code>body</code>
|
|
35
|
+
fields.</dd>
|
|
36
|
+
</dl>
|
|
37
|
+
|
|
38
|
+
<h3>Outputs</h3>
|
|
39
|
+
<ol class="node-ports">
|
|
40
|
+
<li>Passthrough message
|
|
41
|
+
<dl class="message-properties">
|
|
42
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
43
|
+
<dd>The original request message, forwarded unchanged after the response has been
|
|
44
|
+
sent to the Modbus client.</dd>
|
|
45
|
+
|
|
46
|
+
<dt>modbusResponse <span class="property-type">object</span></dt>
|
|
47
|
+
<dd>Details of what this node submitted to <code>respondToRequest</code>, including:
|
|
48
|
+
<ul>
|
|
49
|
+
<li><code>requestId</code>, <code>eventName</code>, <code>address</code>, <code>quantity</code></li>
|
|
50
|
+
<li><code>payloadSubmitted</code> — the exact payload passed to server config</li>
|
|
51
|
+
<li><code>payloadType</code> — payload classification (<code>array</code>, <code>buffer</code>, <code>object</code>, ...)</li>
|
|
52
|
+
<li><code>actualDataSent</code> — normalized values actually encoded for read FC responses</li>
|
|
53
|
+
<li><code>actualDataType</code> — type of <code>actualDataSent</code></li>
|
|
54
|
+
<li><code>ok</code> — whether the response was accepted for the requestId</li>
|
|
55
|
+
<li><code>timestamp</code> — ISO timestamp when this node submitted it</li>
|
|
56
|
+
<li><code>note</code> — present for write FCs to indicate auto-built response behavior</li>
|
|
57
|
+
</ul>
|
|
58
|
+
</dd>
|
|
59
|
+
</dl>
|
|
60
|
+
</li>
|
|
61
|
+
</ol>
|
|
62
|
+
|
|
63
|
+
<h3>Configuration</h3>
|
|
64
|
+
<dl class="message-properties">
|
|
65
|
+
<dt>Register Map <span class="property-type">modbus-registers-config</span></dt>
|
|
66
|
+
<dd>The shared register map to read from (for read requests) or write to (for write requests).
|
|
67
|
+
Required.</dd>
|
|
68
|
+
|
|
69
|
+
</dl>
|
|
70
|
+
|
|
71
|
+
<h3>Details</h3>
|
|
72
|
+
<p>This node handles all eight standard function codes automatically:</p>
|
|
73
|
+
<ul>
|
|
74
|
+
<li><b>FC1 readCoils</b> — reads from the coil register map and returns the values.</li>
|
|
75
|
+
<li><b>FC2 readDiscreteInputs</b> — reads from the discrete input map.</li>
|
|
76
|
+
<li><b>FC3 readHoldingRegisters</b> — reads from the holding register map.</li>
|
|
77
|
+
<li><b>FC4 readInputRegisters</b> — reads from the input register map.</li>
|
|
78
|
+
<li><b>FC5 writeSingleCoil</b> — writes a single bit to the coil map.</li>
|
|
79
|
+
<li><b>FC6 writeSingleRegister</b> — writes a single word to the holding register map.</li>
|
|
80
|
+
<li><b>FC15 writeMultipleCoils</b> — writes multiple bits to the coil map.</li>
|
|
81
|
+
<li><b>FC16 writeMultipleRegisters</b> — writes multiple words to the holding register map.</li>
|
|
82
|
+
</ul>
|
|
83
|
+
<p>If a request addresses a register range that has been set to zero (disabled) in the register
|
|
84
|
+
map config, this node automatically responds with Modbus exception code 2
|
|
85
|
+
(Illegal Data Address). An unknown function code returns exception code 1 (Illegal
|
|
86
|
+
Function). Neither case raises a flow error.</p>
|
|
87
|
+
<p>Flows must preserve <code>msg._modbus</code>:</p>
|
|
88
|
+
<pre><code>// CORRECT
|
|
89
|
+
msg.payload = newValue;
|
|
90
|
+
return msg;
|
|
91
|
+
|
|
92
|
+
// INCORRECT: request context is lost
|
|
93
|
+
msg = { payload: newValue };
|
|
94
|
+
return msg;</code></pre>
|
|
95
|
+
<p>This node is the recommended response handler when using a static register map. For fully
|
|
96
|
+
custom response logic, use the lower-level <code>modbus-dynamic-server-response</code> node
|
|
97
|
+
instead.</p>
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<script type="text/javascript">
|
|
101
|
+
RED.nodes.registerType('modbus-registers-respond', {
|
|
102
|
+
category: 'modbus',
|
|
103
|
+
color: '#E9967A',
|
|
104
|
+
defaults: {
|
|
105
|
+
name: { value: '' },
|
|
106
|
+
registerMap: { value: '', type: 'modbus-registers-config', required: true }
|
|
107
|
+
},
|
|
108
|
+
inputs: 1,
|
|
109
|
+
outputs: 1,
|
|
110
|
+
icon: 'arrow-out.svg',
|
|
111
|
+
label: function () {
|
|
112
|
+
return this.name || 'Modbus Registers Respond'
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
</script>
|
|
116
|
+
|