smithtek-mako-rf 0.0.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/README.md ADDED
@@ -0,0 +1,190 @@
1
+
2
+ **Dedicated PassPort Node that reads and parses the Mako over RF or RS485**
3
+
4
+
5
+
6
+
7
+
8
+ **Installation**
9
+ Install using the `NodeRED palette manager`
10
+
11
+ **Smithtek Mako RF**
12
+ RS-485 / LoRa RF Modbus RTU Master for Mako PLC devices.
13
+
14
+ This node combines Modbus polling, retry logic, queue management and structured decoding into a single industrial block.
15
+
16
+ ---
17
+
18
+ ## Bus Configuration
19
+
20
+ Select the communication channel:
21
+
22
+ - **RS485-1** → /dev/ttyAMA0
23
+ - **RS485-2** → /dev/ttyAMA1
24
+ - **RF** → Long range LoRa radio link
25
+
26
+ When RF is selected, serial parameters are automatically managed.
27
+
28
+ Timeout, Retries and Gap are configured in **seconds**.
29
+
30
+ ---
31
+
32
+ ## Mode
33
+
34
+ ### Read
35
+ Polls registers from the remote Mako and outputs structured JSON.
36
+
37
+ ### Write
38
+ Sends values to the remote device using:
39
+
40
+ - FC5 – Write Single Coil
41
+ - FC6 – Write Single Register
42
+ - FC15 – Write Multiple Coils
43
+ - FC16 – Write Multiple Registers
44
+
45
+ Values can come from `msg.payload` or be fixed in the node configuration.
46
+
47
+ ---
48
+
49
+ ## Device ID
50
+
51
+ The Modbus Unit ID of the remote Mako (1–247).
52
+ Must match the ID configured in the target device.
53
+
54
+ ---
55
+
56
+ ## Function Code
57
+
58
+ Must match the table type configured in your V-NET program:
59
+
60
+ - Read Coils
61
+ - Read Digital Inputs
62
+ - Read Holding Registers
63
+ - Read Input Registers
64
+
65
+ ---
66
+
67
+ ## Address
68
+
69
+ Starting Modbus register address.
70
+ Must align exactly with the Modbus table built in your Mako V-NET program.
71
+
72
+ ---
73
+
74
+ ## Quantity
75
+
76
+ Total number of registers to read.
77
+
78
+ - 16 bit value = 1 register
79
+ - 32 bit value = 2 registers
80
+
81
+ **Example:**
82
+ 3 × 32 bit floats = Quantity 6
83
+
84
+ Quantity must cover all registers defined in your decoder map.
85
+
86
+ ---
87
+
88
+ ## Decoder Map
89
+
90
+ Build the register table in the exact same order as your Mako V-NET Modbus table.
91
+
92
+ Each row defines how raw Modbus data is converted into structured JSON.
93
+
94
+ ### Name
95
+ Becomes the output key in `msg.payload`.
96
+ Nested paths are supported using: `pump=>status`
97
+
98
+ ### Register Data Type
99
+
100
+ - 16 bit integer
101
+ - 16 bit unsigned
102
+ - 32 bit integer
103
+ - 32 bit unsigned
104
+ - 32 bit float
105
+ - Digital to unsigned binary encoder
106
+
107
+ ### Offset (regs)
108
+
109
+ Offset is configured in registers.
110
+
111
+ Automatic stepping behaviour:
112
+
113
+ - 16 bit types increase by 1 register
114
+ - 32 bit types increase by 2 registers
115
+ - Digital encoder increases bit number from 0–15 before moving to the next register
116
+
117
+ Offsets must match your V-NET Modbus table layout exactly.
118
+
119
+ ---
120
+
121
+ ## Digital to Unsigned Binary Encoder
122
+
123
+ Used when one 16 bit register contains multiple digital states.
124
+
125
+ The Bit column becomes active.
126
+ Bit automatically increments when adding rows.
127
+ After Bit 15, the next row moves to the next register.
128
+
129
+ ---
130
+
131
+ ## Scale
132
+
133
+ Optional value transformation.
134
+
135
+ Examples:
136
+
137
+ - `0.1` → multiply by 0.1
138
+ - `>100`
139
+ - `<50`
140
+ - `==1`
141
+ - `<<2`
142
+ - `>>1`
143
+
144
+ If blank, raw decoded value is used.
145
+
146
+ ---
147
+
148
+ ## Output Behaviour
149
+
150
+ ### Successful Read
151
+
152
+ - `msg.payload` → Decoded structured JSON
153
+ - `msg.modbus.raw` → Raw Modbus response
154
+ - `msg.modbus.req` → Request metadata
155
+
156
+ ### Successful Write
157
+
158
+ - `msg.payload` → Device confirmation response
159
+ - `msg.modbus.raw` → Raw Modbus confirmation
160
+
161
+ The write response is the actual Modbus confirmation returned by the remote device.
162
+
163
+ ---
164
+
165
+ ## Industrial Behaviour
166
+
167
+ - Single shared bus queue per channel
168
+ - One request processed at a time
169
+ - Timeout handled in seconds
170
+ - Configurable retry attempts
171
+ - Optional inter-request gap
172
+
173
+ Designed for stable Modbus RTU polling over RS485 or long-range RF links.
174
+
175
+
176
+
177
+ # License
178
+ Copyright (c) 2023 www.smithtek.com.au Licenced under the terms of the GPLv3
179
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
180
+ AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
181
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
182
+ ARE DISCLAIMED. IN NO EVENT SHALL DAMIEN CLARK BE LIABLE FOR ANY DIRECT,
183
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
184
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
185
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
186
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
187
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
188
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
189
+
190
+ www.smithtek.com.au
@@ -0,0 +1,190 @@
1
+
2
+ /**
3
+ * number2bcd -> takes a bcd number and returns the corresponding decimal value
4
+ * @param {Number} number BCD number to convert
5
+ * @param {*} digits no of digits (default 4)
6
+ */
7
+ const bcd2number = function (number, digits = 4) {
8
+ let loByte = (number & 0x00ff);
9
+ let hiByte = (number >> 8) & 0x00ff;
10
+ let n = 0;
11
+ n += (loByte & 0x0F) * 1;
12
+ if (digits < 2) return n;
13
+ n += ((loByte >> 4) & 0x0F) * 10;
14
+ if (digits < 3) return n;
15
+ n += (hiByte & 0x0F) * 100;
16
+ if (digits < 4) return n;
17
+ n += ((hiByte >> 4) & 0x0F) * 1000;
18
+ return n;
19
+ }
20
+
21
+
22
+ /**
23
+ * number2bcd -> takes a number and returns the corresponding BCD value.
24
+ * @param {Number} number number to convert to bcd
25
+ * @param {Number} [digits] no of digits (default 4)
26
+ * @returns {Buffer} nodejs buffer
27
+ */
28
+ const number2bcd = function (number, digits) {
29
+ var s = digits || 4; //default value: 4
30
+ var n = 0;
31
+
32
+ n = (number % 10);
33
+ number = (number / 10) | 0;
34
+ if (s < 2) return n;
35
+ n += (number % 10) << 4;
36
+ number = (number / 10) | 0;
37
+ if (s < 3) return n;
38
+ n += (number % 10) << 8;
39
+ number = (number / 10) | 0;
40
+ if (s < 4) return n;
41
+ n += (number % 10) << 12;
42
+ number = (number / 10) | 0;
43
+ return n;
44
+ }
45
+
46
+ function byteToBits(val) {
47
+ var bits = [];
48
+ for (let index = 0; index < 8; index++) {
49
+ const bit = getBit(val, index);
50
+ bits.push(bit);
51
+ }
52
+
53
+ return {
54
+ bits: bits,
55
+ bit0: bits[0],
56
+ bit1: bits[1],
57
+ bit2: bits[2],
58
+ bit3: bits[3],
59
+ bit4: bits[4],
60
+ bit5: bits[5],
61
+ bit6: bits[6],
62
+ bit7: bits[7],
63
+ }
64
+ }
65
+ function wordToBits(val) {
66
+ var bits = [];
67
+ for (let index = 0; index < 16; index++) {
68
+ const bit = getBit(val, index);
69
+ bits.push(bit);
70
+ }
71
+ return {
72
+ bits: bits,
73
+ bit0: bits[0],
74
+ bit1: bits[1],
75
+ bit2: bits[2],
76
+ bit3: bits[3],
77
+ bit4: bits[4],
78
+ bit5: bits[5],
79
+ bit6: bits[6],
80
+ bit7: bits[7],
81
+ bit8: bits[8],
82
+ bit9: bits[9],
83
+ bit10: bits[10],
84
+ bit11: bits[11],
85
+ bit12: bits[12],
86
+ bit13: bits[13],
87
+ bit14: bits[14],
88
+ bit15: bits[15],
89
+ }
90
+ }
91
+
92
+ //Get Bit
93
+ function getBit(number, bitPosition) {
94
+ return (number & (1 << bitPosition)) === 0 ? 0 : 1;
95
+ }
96
+ //Set Bit
97
+ function setBit(number, bitPosition) {
98
+ return number | (1 << bitPosition);
99
+ }
100
+ //Clear Bit
101
+ function clearBit(number, bitPosition) {
102
+ const mask = ~(1 << bitPosition);
103
+ return number & mask;
104
+ }
105
+ //Update Bit
106
+ function updateBit(number, bitPosition, bitValue) {
107
+ const bitValueNormalized = bitValue ? 1 : 0;
108
+ const clearMask = ~(1 << bitPosition);
109
+ return (number & clearMask) | (bitValueNormalized << bitPosition);
110
+ }
111
+ function bitsToByte(bits) {
112
+ var byte = 0;
113
+ for (let index = 0; index < 8; index++) {
114
+ let bit = bits[index];
115
+ if (bit) byte = setBit(byte, index);
116
+ }
117
+ return byte;
118
+ }
119
+ function bitsToWord(val) {
120
+ var wd = 0;
121
+ for (let index = 0; index < 16; index++) {
122
+ let bit = val[index];
123
+ if (bit) wd = setBit(wd, index);
124
+ }
125
+ return wd;
126
+ }
127
+
128
+ const SWAPOPTS = ["swap16", "swap32", "swap64"];
129
+ const TYPEOPTS = [
130
+ "int", "int8", "byte",
131
+ "uint", "uint8",
132
+ "int16", "int16le", "int16be", "uint16", "uint16le", "uint16be",
133
+ "int32", "int32le", "int32be", "uint32", "uint32le", "uint32be",
134
+ "bigint64", "bigint64le", "bigint64be", "biguint64", "biguint64le", "biguint64be",
135
+ "float", "floatle", "floatbe", "double", "doublele", "doublebe",
136
+ "8bit", "16bit", "16bitle", "16bitbe", "bool",
137
+ "bcd", "bcdle", "bcdbe",
138
+ "string", "hex", "ascii", "utf8", "utf-8", "utf16le", "ucs2", "latin1", "binary", "buffer"
139
+ ];
140
+
141
+ /**
142
+ * helper function to set a nested property by path
143
+ * @param {*} obj - the object in which to set a properties value
144
+ * @param {string} path - the path to the property e.g. payload.value
145
+ * @param {*} val - the value to set in obj.path
146
+ */
147
+ function setObjectProperty(obj, path, val, sep) {
148
+ sep = sep == null ? "." : sep;
149
+ const keys = path.split(sep);
150
+ const lastKey = keys.pop();
151
+ const lastObj = keys.reduce((obj, key) =>
152
+ obj[key] = obj[key] || {},
153
+ obj);
154
+ lastObj[lastKey] = val;
155
+ }
156
+
157
+ /**
158
+ * helper function to get a property by path
159
+ * @param {*} obj - the object in which to set a properties value
160
+ * @param {string} path - the path to the property e.g. payload.value
161
+ * @param {*} [sep] - the path property separator (defaults to `.`)
162
+ */
163
+ function getObjectProperty(obj, path, sep) {
164
+ sep = sep == null ? "." : sep;
165
+ for (var i=0, path=path.split(sep), len=path.length; i<len; i++){
166
+ obj = obj[path[i]];
167
+ };
168
+ return obj;
169
+ }
170
+
171
+ function isNumber(n) {
172
+ if (n === "" || n === true || n === false) return false;
173
+ return !isNaN(parseFloat(n)) && isFinite(n);
174
+ }
175
+
176
+ exports.bcd2number = bcd2number;
177
+ exports.number2bcd = number2bcd;
178
+ exports.byteToBits = byteToBits;
179
+ exports.wordToBits = wordToBits;
180
+ exports.bitsToByte = bitsToByte;
181
+ exports.bitsToWord = bitsToWord;
182
+ exports.getBit = getBit;
183
+ exports.setBit = setBit;
184
+ exports.clearBit = clearBit;
185
+ exports.updateBit = updateBit;
186
+ exports.isNumber = isNumber;
187
+ exports.setObjectProperty = setObjectProperty;
188
+ exports.getObjectProperty = getObjectProperty;
189
+ exports.SWAPOPTS = SWAPOPTS;
190
+ exports.TYPEOPTS = TYPEOPTS;
package/icon.png ADDED
Binary file
package/icons/icon.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "smithtek-mako-rf",
3
+ "version": "0.0.1",
4
+ "description": "Smithtek Mako RF: Modbus RTU (RS-485) over RF with shared bus queue, timeout seconds and retries.",
5
+ "keywords": [
6
+ "node-red",
7
+ "smithtek",
8
+ "modbus",
9
+ "rtu",
10
+ "rs485",
11
+ "lora"
12
+ ],
13
+ "license": "GPL-3.0-only",
14
+ "author": "Smithtek",
15
+ "homepage": "https://www.smithtek.com.au",
16
+ "dependencies": {
17
+ "modbus-serial": "^9.0.10"
18
+ },
19
+ "node-red": {
20
+ "nodes": {
21
+ "smithtek-mako-rf": "smithtek-mako-rf.js"
22
+ }
23
+ }
24
+ }