smithtek-mako-rf 2.9.10 → 3.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/package.json CHANGED
@@ -1,25 +1,25 @@
1
- {
2
- "name": "smithtek-mako-rf",
3
- "version": "2.9.10",
4
- "description": "Smithtek dedicated node for communicating with the Mako PLC over RS485 or RF",
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": "8.0.23-no-serial-port",
18
- "serialport": "10.4.0"
19
- },
20
- "node-red": {
21
- "nodes": {
22
- "smithtek-mako-rf": "smithtek-mako-rf.js"
23
- }
24
- }
25
- }
1
+ {
2
+ "name": "smithtek-mako-rf",
3
+ "version": "3.0.1",
4
+ "description": "Smithtek dedicated node for communicating with the Mako PLC over RS485 or RF",
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": "8.0.23-no-serial-port",
18
+ "serialport": "10.4.0"
19
+ },
20
+ "node-red": {
21
+ "nodes": {
22
+ "smithtek-mako-rf": "smithtek-mako-rf.js"
23
+ }
24
+ }
25
+ }
@@ -185,12 +185,17 @@ state._rssiCleanup = cleanup;
185
185
  }
186
186
 
187
187
  // Scale supports:
188
- // - numeric => multiply
189
- // - operators like ">100", "<<2", "==1", etc.
188
+ // - numeric => multiply (including 0)
189
+ // - operators like "*2", "/10", "+5", "-7", ">100", "<<2", "==1", etc.
190
190
  const scalingOps = {
191
+ "*": (v, o) => v * o,
192
+ "/": (v, o) => v / o,
193
+ "+": (v, o) => v + o,
194
+ "-": (v, o) => v - o,
195
+
191
196
  ">": (v, o) => v > o,
192
197
  "<": (v, o) => v < o,
193
- "==": (v, o) => v == o,
198
+ "==": (v, o) => v === o,
194
199
  "!=": (v, o) => v != o,
195
200
  "%": (v, o) => v % o,
196
201
  "<<": (v, o) => v << o,
@@ -198,16 +203,19 @@ state._rssiCleanup = cleanup;
198
203
  ">>>": (v, o) => v >>> o,
199
204
  };
200
205
 
201
- const scalerRegex = /^\s*([<>!=%]{1,3}|<<|>>>|>>)\s*(-?\d+(\.\d+)?)\s*$/;
206
+ // allow optional leading operator, otherwise a plain number
207
+ const scalerRegex = /^\s*([*\/+\-]|[<>!=%]{1,3}|<<|>>>|>>)\s*(-?\d+(\.\d+)?)\s*$/;
202
208
 
203
209
  function parseScaler(scale) {
204
210
  if (scale == null) return null;
205
211
  const s = String(scale).trim();
206
- if (!s || s === "0" || s === "1") return null;
212
+ if (!s) return null;
207
213
 
208
- if (!Number.isNaN(Number(s))) {
209
- return { op: "*", val: Number(s) };
210
- }
214
+ // Plain numeric means multiply (0 is valid!)
215
+ const asNum = Number(s);
216
+ if (!Number.isNaN(asNum)) return { op: "*", val: asNum };
217
+
218
+ // Operator form: "*2" "/10" "+5" etc
211
219
  const m = s.match(scalerRegex);
212
220
  if (m && m[1] && !Number.isNaN(Number(m[2])) && scalingOps[m[1]]) {
213
221
  return { op: m[1], val: Number(m[2]) };
@@ -222,7 +230,7 @@ state._rssiCleanup = cleanup;
222
230
  // Read a typed value using your Mako dropdown names.
223
231
  // Offset is BYTES (0,2,4...) like your original decoder.
224
232
  // For 32-bit values, auto-detect word/byte order once per decode pass.
225
- function readTyped(regs, type, byteOffset, offsetBit, state32) {
233
+ function readTyped(regs, type, byteOffset, offsetBit) {
226
234
  const t = String(type || "").toLowerCase();
227
235
  const regIndex = Math.floor(byteOffset / 2);
228
236
 
@@ -251,34 +259,11 @@ state._rssiCleanup = cleanup;
251
259
  return Buffer.from([b & 0xff, (b >> 8) & 0xff, a & 0xff, (a >> 8) & 0xff]);
252
260
  }
253
261
 
254
- function pick32OrderIfNeeded(a, b) {
255
- if (state32.order) return state32.order;
256
-
257
- const candidates = [
258
- { o: "ABCD", v: bytesABCD(a, b).readFloatBE(0) },
259
- { o: "CDAB", v: bytesCDAB(a, b).readFloatBE(0) },
260
- { o: "BADC", v: bytesBADC(a, b).readFloatBE(0) },
261
- { o: "DCBA", v: bytesDCBA(a, b).readFloatBE(0) },
262
- ];
263
-
264
- // Sane heuristic: finite, not denormal-tiny, not insane huge
265
- for (const c of candidates) {
266
- if (Number.isFinite(c.v) && Math.abs(c.v) > 1e-6 && Math.abs(c.v) < 1e9) {
267
- state32.order = c.o;
268
- return c.o;
269
- }
270
- }
271
262
 
272
- state32.order = "ABCD";
273
- return state32.order;
274
- }
275
263
 
276
264
  function get32Bytes(a, b) {
277
- const order = pick32OrderIfNeeded(a, b);
278
- if (order === "ABCD") return bytesABCD(a, b);
279
- if (order === "CDAB") return bytesCDAB(a, b);
280
- if (order === "BADC") return bytesBADC(a, b);
281
- return bytesDCBA(a, b);
265
+ // LOCKED CDAB (matches your confirmed raw->decoded results)
266
+ return bytesCDAB(a, b);
282
267
  }
283
268
 
284
269
  switch (t) {
@@ -343,7 +328,7 @@ state._rssiCleanup = cleanup;
343
328
  if (!Array.isArray(regArray) || !Array.isArray(items) || !items.length) return decoded;
344
329
 
345
330
  // Auto-detect 32-bit order on first 32-bit value, then reuse for all
346
- const state32 = { order: null };
331
+ // const state32 = { order: null };
347
332
 
348
333
  for (let i = 0; i < items.length; i++) {
349
334
  const it = items[i] || {};
@@ -359,18 +344,27 @@ state._rssiCleanup = cleanup;
359
344
 
360
345
  let val;
361
346
  try {
362
- val = readTyped(regArray, type, byteOffset, offsetBit, state32);
347
+ val = readTyped(regArray, type, byteOffset, offsetBit);
363
348
  } catch (_e) {
364
349
  continue;
365
350
  }
366
351
 
352
+ // Read scale from multiple possible editor field names (prevents silent "never scales")
353
+ const scaleRaw = (it.scale != null) ? it.scale
354
+ : (it.scaler != null) ? it.scaler
355
+ : (it.scaleFactor != null) ? it.scaleFactor
356
+ : null;
357
+
358
+ // Apply mask ONLY if value is an integer (otherwise it will trash floats)
367
359
  const mask = parseMask(it.mask);
368
- if (mask && typeof val === "number") val = val & mask;
360
+ if (mask && typeof val === "number" && Number.isInteger(val)) {
361
+ val = (val & mask) >>> 0; // keep it sane as unsigned
362
+ }
369
363
 
370
- const scaler = parseScaler(it.scale);
371
- if (scaler) {
372
- if (scaler.op === "*" && typeof val === "number") val = val * scaler.val;
373
- else if (scaler.op && scalingOps[scaler.op]) val = scalingOps[scaler.op](val, scaler.val);
364
+ const scaler = parseScaler(scaleRaw);
365
+ if (scaler && typeof val === "number") {
366
+ const fn = scalingOps[scaler.op] || scalingOps["*"];
367
+ val = fn(val, scaler.val);
374
368
  }
375
369
 
376
370
  setObjectProperty(decoded, name, val, "=>");
Binary file
Binary file