smithtek-mako-rf 3.2.4 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -45
- package/package.json +1 -1
- package/smithtek-mako-rf.html +15 -1
- package/smithtek-mako-rf.js +75 -15
package/README.md
CHANGED
|
@@ -1,47 +1,3 @@
|
|
|
1
|
-
Digital read outputs in msg.payload use numeric 1 (on) and 0 (off), including coils (FC1), discrete inputs (FC2), and individual register bits. The raw Modbus response retains its original representation. Coil writes still accept true/false and 1/0.
|
|
2
|
-
|
|
3
|
-
# Simple bus settings and range scaling — 3.2.4
|
|
4
|
-
|
|
5
|
-
The bus editor retains the original timeout, retries, gap, maximum queue and RSSI controls. Queue age, coalescing, offline probe and recovery-quiet controls have been removed. Any saved values for those removed controls are ignored. All accepted requests wait in FIFO order; recovery timing is managed internally. The Range option in the decoder table remains available.
|
|
6
|
-
|
|
7
|
-
Each numeric decoder row has a compact Scaling dropdown: None (unchanged), Math (+ − × ÷), or Range (input → output). New rows start at None, with all calculation fields hidden. Math and Range settings appear beside the dropdown on the same line. Range shows In low, In high, Out low, Out high and Clamp side by side. Every decoder entry stays on one line; narrow dialogs scroll horizontally. Selecting None hides the fields and disables both calculations, retaining the entered settings for reuse. Existing configured Math and Range rows keep their active mode.
|
|
8
|
-
|
|
9
|
-
For Range, enter Input low, Input high, Output low and Output high. For example:
|
|
10
|
-
|
|
11
|
-
| Input low | Input high | Output low | Output high | Result at input 12 |
|
|
12
|
-
|---|---|---|---|---|
|
|
13
|
-
| 4 | 20 | 0 | 100 | 50 |
|
|
14
|
-
|
|
15
|
-
The input endpoints must match the values reported in Modbus registers. If a sensor reports 4000–20000 instead of 4–20, enter 4000 and 20000. No PLC calculation or extra Function node is needed.
|
|
16
|
-
|
|
17
|
-
Range scaling uses outputLow + (value - inputLow) * (outputHigh - outputLow) / (inputHigh - inputLow). It operates on the decoded numeric value after any existing integer mask. Range replaces the Math expression, retaining that expression for switching back. Negative, decimal and reversed ranges are supported. Equal input endpoints and invalid/nonfinite values are rejected. Digital/boolean rows cannot use Range mode.
|
|
18
|
-
|
|
19
|
-
Values outside the input span continue along the same line unless Clamp to output range is checked. For example, 0 mA maps to -25 for a 4–20 to 0–100 range; with Clamp it maps to 0. Floating-point results are not automatically rounded.
|
|
20
|
-
|
|
21
|
-
Existing rows without a scaling mode continue using Math. Raw Modbus data remains available in msg.modbus.raw. All FIFO queue and recovery behaviour from 3.1.2 is retained, and dependencies are unchanged.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
# Reliability update 3.1.2
|
|
25
|
-
|
|
26
|
-
This release retains the pinned serial dependencies and supports Node.js 14 syntax.
|
|
27
|
-
|
|
28
|
-
- One worker owns each bus. Closing a runtime node cancels only its own work.
|
|
29
|
-
- Open, request, diagnostic and close operations have deadlines. A port whose close cannot be confirmed stays quarantined rather than acquiring a second owner.
|
|
30
|
-
- Serial errors are handled. Dead connections are retired before reopening.
|
|
31
|
-
- Writes use FIFO order. Queue overflow, expiry and superseded polls return explicit errors and complete once.
|
|
32
|
-
- Every accepted request is retained in FIFO order, including repeated reads from the same node. Requests are not coalesced or expired while waiting.
|
|
33
|
-
- Each queued request gets the configured timeout retry count before moving to the next. There is no offline cooldown or skipped polling.
|
|
34
|
-
- After a communication error, the bus waits for a quiet period and flushes stale buffered data. Automatic quiet time is the response timeout on RF and 100ms on RS485. Late RF frames outside that interval remain a protocol limitation: Modbus RTU read replies carry no register address or transaction ID.
|
|
35
|
-
- RSSI queries verify their checksum, isolate diagnostic bytes, settle on cancellation and retain the serial connection after success.
|
|
36
|
-
- Invalid addresses, quantities and write values are rejected before transmission. Signed scale expressions add/subtract, and incomplete 32-bit values are rejected.
|
|
37
|
-
|
|
38
|
-
No additional bus settings are required. Recovery timing is automatic: the response timeout on RF and 100ms on RS485.
|
|
39
|
-
|
|
40
|
-
Failures still emit msg.modbus.ok=false and a failure payload. msg.modbus.code identifies queue, offline and transport conditions; done(error) also enables Node-RED Catch handling. Intentional node/bus shutdown settles callbacks without emitting from closed nodes. No retry can guarantee a timed-out write was not applied at the device: retain an appropriate retry setting for the PLC command semantics.
|
|
41
|
-
|
|
42
|
-
Validated on a Passport with Raspbian Buster, Node.js 14.21.3, native serial dependencies and two physical RF Makos. Node.js 16 has not been separately executed. No dependency upgrade is required.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
1
|
**Installation**
|
|
46
2
|
Install using the `NodeRED palette manager`
|
|
47
3
|
|
|
@@ -159,6 +115,12 @@ Each row defines how raw Modbus data is converted into structured JSON.
|
|
|
159
115
|
### Name
|
|
160
116
|
Becomes the output key in `msg.payload`. The name could be your sensor name like "level sensor" or " thermal trip" " pump control" choose a name that suits your SCADA scheme.
|
|
161
117
|
|
|
118
|
+
### Prefix
|
|
119
|
+
|
|
120
|
+
The Prefix field applies to every name in the decoder table. Enter `b1` and a row named `Pressure` becomes `b1 Pressure` in `msg.payload`, with a space between the prefix and name.
|
|
121
|
+
|
|
122
|
+
Copy the node and change only the prefix to reuse the same table for another asset. Leave it blank to use the table names as entered.
|
|
123
|
+
|
|
162
124
|
### Register Data Type
|
|
163
125
|
|
|
164
126
|
- 16 bit integer
|
|
@@ -194,6 +156,16 @@ After Bit 15, the next row moves to the next register.
|
|
|
194
156
|
Optional value transformation applied after the Modbus register is read.
|
|
195
157
|
The Scale field lets you adjust raw register data into meaningful engineering values without needing extra function nodes. Think of it as lightweight post-processing built directly into the driver.
|
|
196
158
|
|
|
159
|
+
Choose a scaling mode for each row:
|
|
160
|
+
|
|
161
|
+
- **None** leaves the value unchanged.
|
|
162
|
+
- **Math** applies an operator and number, such as `/100` or `+5`.
|
|
163
|
+
- **Range** maps input low/high values to output low/high values.
|
|
164
|
+
|
|
165
|
+
All controls stay on the same row. Selecting None disables the calculation while keeping the entered settings for reuse.
|
|
166
|
+
|
|
167
|
+
### Math
|
|
168
|
+
|
|
197
169
|
You can apply the following operators:
|
|
198
170
|
|
|
199
171
|
- Addition +
|
|
@@ -223,11 +195,48 @@ Example:
|
|
|
223
195
|
|
|
224
196
|
If you want to multiple your modbus value by 10 you would put "* 10" without the quotes and use a space between the number and the match function.
|
|
225
197
|
|
|
198
|
+
### Range — 4–20 mA Scaling
|
|
199
|
+
|
|
200
|
+
Select **Range** and enter **In low**, **In high**, **Out low** and **Out high**. This converts a sensor reading into engineering units in the Passport without a separate Function node or a calculation in the Mako.
|
|
201
|
+
|
|
202
|
+
For a 4–20 mA signal representing 0–100%:
|
|
203
|
+
|
|
204
|
+
| In low | In high | Out low | Out high |
|
|
205
|
+
|---|---|---|---|
|
|
206
|
+
| 4 | 20 | 0 | 100 |
|
|
207
|
+
|
|
208
|
+
A reading of 4 becomes 0, 12 becomes 50, and 20 becomes 100.
|
|
209
|
+
|
|
210
|
+
Use the values actually reported by the register. If it reports 4000–20000, enter 4000 and 20000 as the input limits instead of 4 and 20.
|
|
211
|
+
|
|
212
|
+
Tick **Clamp** to keep the result within the output limits. Otherwise, values outside the input limits continue scaling beyond the output range. Input low and high must differ. Negative, decimal and reversed ranges are supported. Range applies to numeric values and replaces the Math calculation for that row.
|
|
213
|
+
|
|
226
214
|
This allows you to convert raw Modbus numbers into readable, usable data straight from the node — reducing additional processing and keeping flows clean and simple.
|
|
227
215
|
|
|
228
216
|
---
|
|
229
217
|
|
|
230
|
-
## Output Behaviour
|
|
218
|
+
## Output Behaviour
|
|
219
|
+
|
|
220
|
+
### Output Pins
|
|
221
|
+
|
|
222
|
+
- **Top pin — Data / alarm:** sends parsed Modbus readings or write confirmations. If a request fails, it sends the friendly fault JSON for the cloud instead.
|
|
223
|
+
- **Bottom pin — Debug:** sends technical error details for a local Debug node. Leave it disconnected if these details are not needed. Successful requests send nothing from this pin.
|
|
224
|
+
|
|
225
|
+
### Fault Messages
|
|
226
|
+
|
|
227
|
+
Example payload from the top pin:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{"b1 RF ID1 Not communicating — check power/RF; disable if out of service":1}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The fault name identifies the prefix, bus and device ID, followed by an operator message. If the prefix is blank, the node Name is used. Other problems, such as an unsupported register or invalid settings, receive a message appropriate to that fault.
|
|
234
|
+
|
|
235
|
+
A timeout fault is sent after one poll exhausts its retries: retries set to 0 means one failed attempt; retries set to 2 means three failed attempts. A successful retry sends normal data only. An unsupported-register error is reported immediately because retrying cannot correct the address. Each failed poll sends a fault; a redeploy cancellation does not.
|
|
236
|
+
|
|
237
|
+
The bottom pin carries `msg.payload.ok`, `msg.payload.error`, `msg.payload.code` and `msg.payload.req` for troubleshooting. Technical details are also available in `msg.modbus`. Node-RED error logs and Catch handling remain available.
|
|
238
|
+
|
|
239
|
+
When communication succeeds again, the top pin resumes normal readings. No automatic zero/clear alarm is sent.
|
|
231
240
|
|
|
232
241
|
### Successful Read
|
|
233
242
|
|
package/package.json
CHANGED
package/smithtek-mako-rf.html
CHANGED
|
@@ -369,6 +369,7 @@
|
|
|
369
369
|
color: "#B9B6B8",
|
|
370
370
|
defaults: {
|
|
371
371
|
name: { value: "" },
|
|
372
|
+
prefix: { value: "" },
|
|
372
373
|
bus: { type: "smithtek-mako-rf-bus", required: true },
|
|
373
374
|
|
|
374
375
|
mode: { value: "read" },
|
|
@@ -385,7 +386,8 @@
|
|
|
385
386
|
items: { value: [ defaultItemRow() ], validate: validDecoderItems }
|
|
386
387
|
},
|
|
387
388
|
inputs: 1,
|
|
388
|
-
outputs:
|
|
389
|
+
outputs: 2,
|
|
390
|
+
outputLabels: ["Data / alarm", "Debug"],
|
|
389
391
|
icon: "bridge.svg",
|
|
390
392
|
label: function () { return this.name || "Smithtek Mako RF"; },
|
|
391
393
|
|
|
@@ -547,6 +549,12 @@
|
|
|
547
549
|
<input type="text" id="node-input-name" placeholder="">
|
|
548
550
|
</div>
|
|
549
551
|
|
|
552
|
+
<div class="form-row">
|
|
553
|
+
<label for="node-input-prefix"><i class="fa fa-tag"></i> Prefix</label>
|
|
554
|
+
<input type="text" id="node-input-prefix" placeholder="e.g. b1">
|
|
555
|
+
<div class="form-tips">Added before each decoder name with a space: b1 + Pressure becomes b1 Pressure. Leave blank to keep existing names.</div>
|
|
556
|
+
</div>
|
|
557
|
+
|
|
550
558
|
<div class="form-row">
|
|
551
559
|
<label for="node-input-bus"><i class="fa fa-sitemap"></i> Bus</label>
|
|
552
560
|
<input type="text" id="node-input-bus" style="width: 25%;">
|
|
@@ -673,5 +681,11 @@ Smithtek Mako RF / RS485.
|
|
|
673
681
|
Read mode outputs decoded JSON in msg.payload using the Decoder map rows. Digital points (coils, discrete inputs and register bits) output numeric 1 for on and 0 for off.
|
|
674
682
|
Raw modbus response is kept in msg.modbus.raw.
|
|
675
683
|
|
|
684
|
+
Prefix adds text and one space before each decoder name. Leave blank to preserve names.
|
|
685
|
+
|
|
686
|
+
Output 1 (Data / alarm) carries successful readings and write responses, or the friendly named fault JSON on failure. Output 2 (Debug) carries technical error details: ok, error, code and req. Connect it to a local Debug node or leave it disconnected. Node-RED error logs and Catch handling remain enabled. A timeout alarm is sent after one poll exhausts its retries (retries 2 means 3 attempts), not after multiple bad polls. Permanent Modbus exceptions report immediately. Each failed poll emits one alarm and one debug message.
|
|
687
|
+
|
|
688
|
+
Failed requests output a named numeric alarm on output 1, for example {"b1 RF ID2 Not communicating — check power/RF; disable if out of service":1}. Details remain in msg.modbus, including ok, code, error and fault. Only final failures are reported; a successful retry or intentional shutdown does not emit an alarm. Successful reads resume normal data; no automatic alarm-clear value is sent.
|
|
689
|
+
|
|
676
690
|
Each numeric decoder row starts compact with None (unchanged). Choose Math or Range to show the inline settings and enable scaling. Selecting None hides both controls and disables both calculations, while retaining the entered settings. Range maps input low/high to output low/high using a straight line; for example 4–20 maps to 0–100, with 12 mapping to 50. Use raw register endpoints, not assumed electrical units. Input endpoints must differ. Reversed ranges and negative/decimal endpoints are supported. Clamp optionally limits output to the configured range. Existing rows without a scaling mode retain their Math expression.
|
|
677
691
|
</script>
|
package/smithtek-mako-rf.js
CHANGED
|
@@ -78,7 +78,7 @@ function toNum(v, fallback) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Nested property setter using delimiter "=>"
|
|
81
|
-
function setObjectProperty(obj, path, value, delimiter) {
|
|
81
|
+
function setObjectProperty(obj, path, value, delimiter, prefix) {
|
|
82
82
|
if (!path || typeof path !== "string") {
|
|
83
83
|
obj[path] = value;
|
|
84
84
|
return;
|
|
@@ -91,6 +91,9 @@ function toNum(v, fallback) {
|
|
|
91
91
|
if (!parts.length) return;
|
|
92
92
|
|
|
93
93
|
if (parts.some(k => ["__proto__","prototype","constructor"].includes(k))) throw new Error("Invalid decoder property path");
|
|
94
|
+
// Prefix is literal text, even if it contains the nesting delimiter.
|
|
95
|
+
const labelPrefix = String(prefix == null ? '' : prefix).trim();
|
|
96
|
+
if (labelPrefix) parts[0] = labelPrefix + ' ' + parts[0];
|
|
94
97
|
let ref = obj;
|
|
95
98
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
96
99
|
const k = parts[i];
|
|
@@ -252,7 +255,7 @@ function toNum(v, fallback) {
|
|
|
252
255
|
}
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
function decodeWithItems(regArray, items) {
|
|
258
|
+
function decodeWithItems(regArray, items, prefix) {
|
|
256
259
|
const decoded = {};
|
|
257
260
|
if (!Array.isArray(regArray) || !Array.isArray(items) || !items.length) return decoded;
|
|
258
261
|
|
|
@@ -305,7 +308,7 @@ function toNum(v, fallback) {
|
|
|
305
308
|
// Digital points are numeric at the output boundary. Keep the internal
|
|
306
309
|
// boolean until here so saved math/masks cannot change a bit into another value.
|
|
307
310
|
if (typeof val === 'boolean') val = val ? 1 : 0;
|
|
308
|
-
setObjectProperty(decoded, name, val, "=>");
|
|
311
|
+
setObjectProperty(decoded, name, val, "=>", prefix);
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
return decoded;
|
|
@@ -370,6 +373,45 @@ function toNum(v, fallback) {
|
|
|
370
373
|
return req;
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
function faultInfo(err, config, cfg, req) {
|
|
377
|
+
const code = err.code || (err.modbusCode ? 'MODBUS_EXCEPTION_'+err.modbusCode : 'ECOMM');
|
|
378
|
+
const message = String(err.message || err);
|
|
379
|
+
const labels = {ECONFIG:'Configuration error',EDECODE:'Decode error',EQUEUEFULL:'Queue full',
|
|
380
|
+
EQUEUEEXPIRED:'Queue expired',EOFFLINE:'Offline',EPORTBUSY:'Port busy',ECLOSE:'Serial close failure',
|
|
381
|
+
EDISCONNECT:'Serial disconnected',EIO:'Serial error',ENOENT:'Serial port unavailable',EACCES:'Serial permission denied'};
|
|
382
|
+
let reason = labels[code];
|
|
383
|
+
if (!reason && err.modbusCode) reason = 'Modbus exception '+err.modbusCode;
|
|
384
|
+
if (!reason && (code === 'ETIMEDOUT' || /timed?\s*out|timeout/i.test(message))) reason = 'Timeout';
|
|
385
|
+
if (!reason && code === 'EDEADLINE') reason = /request/i.test(message) ? 'Timeout' : 'Operation deadline';
|
|
386
|
+
if (!reason && /crc/i.test(message)) reason = 'CRC error';
|
|
387
|
+
if (!reason) reason = 'Communication error';
|
|
388
|
+
const port = cfg && cfg.serialPort;
|
|
389
|
+
const channel = {'/dev/ttyAMA2':'RF','/dev/ttyAMA0':'RS485-1','/dev/ttyAMA1':'RS485-2'}[port] || (cfg && cfg.busName) || 'Bus';
|
|
390
|
+
const prefix = String(config.prefix == null ? '' : config.prefix).trim();
|
|
391
|
+
const label = prefix || String(config.name || '').trim();
|
|
392
|
+
const unitid = req ? req.unitid : (config.unitid == null ? 1 : config.unitid);
|
|
393
|
+
const link = channel === 'RF' ? 'RF' : /^RS485/.test(channel) ? 'RS485 wiring' : 'connection';
|
|
394
|
+
const advice = {
|
|
395
|
+
'Timeout':'Not communicating — check power/'+link+'; disable if out of service',
|
|
396
|
+
'Configuration error':'Setup error — check node settings',
|
|
397
|
+
'Decode error':'Data format error — check register map/scaling',
|
|
398
|
+
'Queue full':'Polling overloaded — reduce polling rate',
|
|
399
|
+
'Queue expired':'Polling delayed — reduce polling rate',
|
|
400
|
+
'Offline':'Polling paused — check device availability',
|
|
401
|
+
'Port busy':'Bus in use — check duplicate bus settings',
|
|
402
|
+
'Serial close failure':'Gateway port fault — check bus connection',
|
|
403
|
+
'Serial disconnected':'Gateway disconnected — check bus connection',
|
|
404
|
+
'Serial error':'Gateway port fault — check bus connection',
|
|
405
|
+
'Serial port unavailable':'Gateway port missing — check bus settings',
|
|
406
|
+
'Serial permission denied':'Gateway port blocked — check access permissions',
|
|
407
|
+
'Operation deadline':'Gateway operation stalled — check bus connection',
|
|
408
|
+
'CRC error':'Invalid reply — check '+link+' link'
|
|
409
|
+
};
|
|
410
|
+
const summary = advice[reason] || (err.modbusCode ? 'Request rejected — check function/register settings' : 'Communication fault — check power/'+link);
|
|
411
|
+
const key = [label,channel,'ID'+unitid,summary].filter(Boolean).join(' ');
|
|
412
|
+
return {key,code,message,reason,summary,channel,unitid,prefix,node:config.name || '',timestamp:new Date().toISOString()};
|
|
413
|
+
}
|
|
414
|
+
|
|
373
415
|
function SmithtekMakoRfNode(config) {
|
|
374
416
|
RED.nodes.createNode(this,config);
|
|
375
417
|
const node = this;
|
|
@@ -383,23 +425,41 @@ function toNum(v, fallback) {
|
|
|
383
425
|
const cfg = RED.nodes.getNode(config.bus);
|
|
384
426
|
let req;
|
|
385
427
|
const complete = (err,res) => {
|
|
428
|
+
let failure = err;
|
|
386
429
|
try {
|
|
387
|
-
if(node._makoClosed || (
|
|
388
|
-
|
|
389
|
-
if(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
430
|
+
if(node._makoClosed || (failure && failure.code === 'ECANCELLED')) return;
|
|
431
|
+
let payload;
|
|
432
|
+
if (!failure) {
|
|
433
|
+
try {
|
|
434
|
+
payload = req.mode === 'read' && res && Array.isArray(res.data) ? decodeWithItems(res.data,config.items || [],config.prefix) : res;
|
|
435
|
+
} catch (decodeError) {
|
|
436
|
+
failure = decodeError;
|
|
437
|
+
failure.code = 'EDECODE';
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
msg.modbus = {ok:!failure,bus:cfg && cfg.busName,req};
|
|
441
|
+
if(res) msg.modbus.raw = res;
|
|
442
|
+
if(failure) {
|
|
443
|
+
const fault = faultInfo(failure,config,cfg,req);
|
|
444
|
+
msg.modbus.error = fault.message;
|
|
445
|
+
msg.modbus.code = fault.code;
|
|
446
|
+
msg.modbus.fault = fault;
|
|
447
|
+
msg.payload = {[fault.key]:1};
|
|
448
|
+
node.status({fill:failure.code === 'EOFFLINE' ? 'yellow' : 'red',shape:'ring',text:fault.message});
|
|
394
449
|
} else {
|
|
395
|
-
msg.
|
|
396
|
-
msg.payload = req.mode === 'read' && res && Array.isArray(res.data) ? decodeWithItems(res.data,config.items || []) : res;
|
|
450
|
+
msg.payload = payload;
|
|
397
451
|
if(msg._makoRssi != null) {msg.payload[sanitizeKey(node.name || config.name || cfg.busName)+'_rssi_dbm']=msg._makoRssi;delete msg._makoRssi;}
|
|
398
452
|
node.status({fill:'green',shape:'dot',text:'ok uid'+req.unitid});
|
|
399
453
|
}
|
|
400
|
-
|
|
454
|
+
// Cloud output always carries either decoded data or the friendly alarm.
|
|
455
|
+
// The second output contains a separate technical error payload.
|
|
456
|
+
const debugMsg = failure ? Object.assign({}, msg, {
|
|
457
|
+
payload: {ok:false,error:msg.modbus.error,code:msg.modbus.code,req},
|
|
458
|
+
modbus: Object.assign({}, msg.modbus)
|
|
459
|
+
}) : null;
|
|
460
|
+
send([msg, debugMsg]);
|
|
401
461
|
} catch(e) {completeOnce(e);return;}
|
|
402
|
-
finally {completeOnce(
|
|
462
|
+
finally {completeOnce(failure || undefined);}
|
|
403
463
|
};
|
|
404
464
|
try {
|
|
405
465
|
req=requestFor(config,msg);
|
|
@@ -417,7 +477,7 @@ function toNum(v, fallback) {
|
|
|
417
477
|
catch(e){if(e.code === 'ECANCELLED')throw e;msg.rssi_error=e.message;}
|
|
418
478
|
}
|
|
419
479
|
}});
|
|
420
|
-
}catch(err){complete(err);}
|
|
480
|
+
}catch(err){if(!err.code) err.code='ECONFIG';complete(err);}
|
|
421
481
|
});
|
|
422
482
|
node.on('close',(_removed,done)=>{
|
|
423
483
|
node._makoClosed=true;
|