@machinemetrics/io-adapter-lib 2.32.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.
Files changed (264) hide show
  1. package/.circleci/config.yml +141 -0
  2. package/.eslintrc.json +36 -0
  3. package/.gitattributes +12 -0
  4. package/CHANGELOG.md +544 -0
  5. package/README.md +2 -0
  6. package/index.js +17 -0
  7. package/lib/config/adapterConfig.js +535 -0
  8. package/lib/config/common/allowDenyList.js +58 -0
  9. package/lib/config/common/jsonPath.js +44 -0
  10. package/lib/config/common/labjackU3T4Common.js +227 -0
  11. package/lib/config/common/validations.js +142 -0
  12. package/lib/config/configError.js +32 -0
  13. package/lib/config/device/adam-6052Config.js +103 -0
  14. package/lib/config/device/brotherHTTPConfig.js +215 -0
  15. package/lib/config/device/ethernetIPConfig.js +191 -0
  16. package/lib/config/device/ifmIotConfig.js +245 -0
  17. package/lib/config/device/jsonHttpConfig.js +76 -0
  18. package/lib/config/device/labjackT4Config.js +39 -0
  19. package/lib/config/device/labjackT7Config.js +192 -0
  20. package/lib/config/device/labjackU3Config.js +32 -0
  21. package/lib/config/device/modbusTcpConfig.js +336 -0
  22. package/lib/config/device/mqttBaseConfig.js +70 -0
  23. package/lib/config/device/mqttConfig.js +113 -0
  24. package/lib/config/device/mqttLincolnConfig.js +62 -0
  25. package/lib/config/device/mtconnectAdapterConfig.js +42 -0
  26. package/lib/config/device/mtconnectBaseConfig.js +136 -0
  27. package/lib/config/device/mtconnectConfig.js +52 -0
  28. package/lib/config/device/mtconnectHaasConfig.js +15 -0
  29. package/lib/config/device/nullConfig.js +81 -0
  30. package/lib/config/device/opcuaConfig.js +205 -0
  31. package/lib/config/device/pcccConfig.js +88 -0
  32. package/lib/config/engineConfigV1.js +382 -0
  33. package/lib/config/engineConfigV2.js +106 -0
  34. package/lib/config/generator/counterGenConfig.js +15 -0
  35. package/lib/config/generator/cronGenConfig.js +33 -0
  36. package/lib/config/generator/dateTimeGenConfig.js +33 -0
  37. package/lib/config/index.js +339 -0
  38. package/lib/config/transformConfigUtil.js +296 -0
  39. package/lib/engine/dataOutput.js +357 -0
  40. package/lib/engine/deviceOutput.js +186 -0
  41. package/lib/engine/engineV1.js +480 -0
  42. package/lib/engine/engineV2.js +719 -0
  43. package/lib/engine/index.js +34 -0
  44. package/lib/engine/transformBuilderV1.js +111 -0
  45. package/lib/engine/transformBuilderV2.js +74 -0
  46. package/lib/expressionService.js +330 -0
  47. package/lib/math.js +142 -0
  48. package/lib/transform/accumulate.js +98 -0
  49. package/lib/transform/average.js +56 -0
  50. package/lib/transform/debounce.js +152 -0
  51. package/lib/transform/downsample.js +69 -0
  52. package/lib/transform/edge.js +34 -0
  53. package/lib/transform/expression.js +91 -0
  54. package/lib/transform/fallingEdge.js +70 -0
  55. package/lib/transform/fromBuffer.js +89 -0
  56. package/lib/transform/hash.js +41 -0
  57. package/lib/transform/ignoreValue.js +118 -0
  58. package/lib/transform/index.js +93 -0
  59. package/lib/transform/invert.js +25 -0
  60. package/lib/transform/latch.js +99 -0
  61. package/lib/transform/latchValue.js +115 -0
  62. package/lib/transform/logicAnd.js +67 -0
  63. package/lib/transform/logicOr.js +67 -0
  64. package/lib/transform/map.js +115 -0
  65. package/lib/transform/max.js +57 -0
  66. package/lib/transform/maxLength.js +39 -0
  67. package/lib/transform/min.js +57 -0
  68. package/lib/transform/minDelta.js +40 -0
  69. package/lib/transform/offDelay.js +89 -0
  70. package/lib/transform/onDelay.js +89 -0
  71. package/lib/transform/patternEscape.js +24 -0
  72. package/lib/transform/patternMatch.js +194 -0
  73. package/lib/transform/patternReplace.js +170 -0
  74. package/lib/transform/patternTest.js +85 -0
  75. package/lib/transform/rateOfChange.js +56 -0
  76. package/lib/transform/reject.js +115 -0
  77. package/lib/transform/resample.js +96 -0
  78. package/lib/transform/risingEdge.js +90 -0
  79. package/lib/transform/risingEdgeCounter.js +179 -0
  80. package/lib/transform/sampleInterval.js +12 -0
  81. package/lib/transform/source.js +116 -0
  82. package/lib/transform/state.js +201 -0
  83. package/lib/transform/threshold.js +52 -0
  84. package/lib/transform/toBuffer.js +159 -0
  85. package/lib/transform/toggle.js +118 -0
  86. package/lib/transform/transformState.js +193 -0
  87. package/lib/transform/trim.js +27 -0
  88. package/lib/transform/util/chainSource.js +96 -0
  89. package/lib/transform/util/ringBuffer.js +34 -0
  90. package/lib/transform/valueChange.js +34 -0
  91. package/lib/transform/valueDecrease.js +38 -0
  92. package/lib/transform/valueIncrease.js +38 -0
  93. package/lib/transform/valueIncreaseDiff.js +66 -0
  94. package/lib/transform/whenUnavailable.js +73 -0
  95. package/lib/transform/windowCount.js +86 -0
  96. package/lib/util/fileUtil.js +44 -0
  97. package/package.json +38 -0
  98. package/test/.eslintrc.json +15 -0
  99. package/test/chainedTransform.test.js +88 -0
  100. package/test/conditions.test.js +118 -0
  101. package/test/config/ab-pccc.test.js +41 -0
  102. package/test/config/adam-6052.test.js +16 -0
  103. package/test/config/adapter.test.js +109 -0
  104. package/test/config/brother-http.test.js +19 -0
  105. package/test/config/ethernet-ip.test.js +19 -0
  106. package/test/config/ifm-iot.test.js +20 -0
  107. package/test/config/json-http.test.js +47 -0
  108. package/test/config/labjack-t4.test.js +78 -0
  109. package/test/config/labjack-t7.test.js +18 -0
  110. package/test/config/labjack-u3.test.js +17 -0
  111. package/test/config/modbusTcp.test.js +87 -0
  112. package/test/config/mqtt.test.js +19 -0
  113. package/test/config/mqttLincoln.test.js +29 -0
  114. package/test/config/mtconnect.test.js +63 -0
  115. package/test/config/mtconnectAdapter.test.js +124 -0
  116. package/test/config/mtconnectHaas.test.js +15 -0
  117. package/test/config/null.test.js +16 -0
  118. package/test/config/opcua.test.js +97 -0
  119. package/test/config-tests.js +102 -0
  120. package/test/configFiles/conditions.yml +37 -0
  121. package/test/configFiles/data-items-legacy.yml +24 -0
  122. package/test/configFiles/data-items-shorthand.yml +14 -0
  123. package/test/configFiles/data-items.yml +12 -0
  124. package/test/configFiles/device/ab-pccc-default.yml +3 -0
  125. package/test/configFiles/device/ab-pccc-full.yml +13 -0
  126. package/test/configFiles/device/adam-6052-default.yml +2 -0
  127. package/test/configFiles/device/brother-http-default.yml +3 -0
  128. package/test/configFiles/device/ethernet-ip-default.yml +2 -0
  129. package/test/configFiles/device/ifm-iot-default.yml +2 -0
  130. package/test/configFiles/device/json-http-bad-prop.yml +13 -0
  131. package/test/configFiles/device/json-http-bad-prop2.yml +13 -0
  132. package/test/configFiles/device/json-http-default.yml +3 -0
  133. package/test/configFiles/device/json-http-std.yml +13 -0
  134. package/test/configFiles/device/labjack-t4-condition-exprs.yaml +46 -0
  135. package/test/configFiles/device/labjack-t4-default.yml +3 -0
  136. package/test/configFiles/device/labjack-t4-pins-alt.yaml +41 -0
  137. package/test/configFiles/device/labjack-t4-pins.yaml +40 -0
  138. package/test/configFiles/device/labjack-t4-v1.yaml +29 -0
  139. package/test/configFiles/device/labjack-t7-default.yml +2 -0
  140. package/test/configFiles/device/labjack-u3-default.yml +2 -0
  141. package/test/configFiles/device/modbus-partial.yml +4 -0
  142. package/test/configFiles/device/modbus-std-extended.yaml +23 -0
  143. package/test/configFiles/device/modbus-std.yaml +34 -0
  144. package/test/configFiles/device/modbus-tcp-default.yml +3 -0
  145. package/test/configFiles/device/mqtt-default.yml +2 -0
  146. package/test/configFiles/device/mqtt-lincoln-default.yml +3 -0
  147. package/test/configFiles/device/mqtt-lincoln-full.yml +7 -0
  148. package/test/configFiles/device/mtconnect-adapter-default.yml +3 -0
  149. package/test/configFiles/device/mtconnect-adapter-keys.yaml +18 -0
  150. package/test/configFiles/device/mtconnect-complex-keys.yaml +17 -0
  151. package/test/configFiles/device/mtconnect-default.yml +3 -0
  152. package/test/configFiles/device/mtconnect-duplicate-allow-keys.yaml +10 -0
  153. package/test/configFiles/device/mtconnect-duplicate-declare-keys.yaml +8 -0
  154. package/test/configFiles/device/mtconnect-duplicate-deny-keys.yaml +10 -0
  155. package/test/configFiles/device/mtconnect-haas-default.yml +3 -0
  156. package/test/configFiles/device/mtconnect-std.yaml +18 -0
  157. package/test/configFiles/device/null-default.yml +1 -0
  158. package/test/configFiles/device/opcua-bad-tag.yml +18 -0
  159. package/test/configFiles/device/opcua-bad-tag2.yml +18 -0
  160. package/test/configFiles/device/opcua-default.yml +3 -0
  161. package/test/configFiles/device/opcua-std.yml +18 -0
  162. package/test/configFiles/dump-test.yml +11 -0
  163. package/test/configFiles/expressionCond.yml +46 -0
  164. package/test/configFiles/min-config-t4.yaml +4 -0
  165. package/test/configFiles/min-config-u3.yaml +3 -0
  166. package/test/configFiles/missing-device.yaml +2 -0
  167. package/test/configFiles/parse-error1.yml +9 -0
  168. package/test/configFiles/parse-error2.yml +9 -0
  169. package/test/configFiles/repro/buffer-convert-repro.yml +15 -0
  170. package/test/configFiles/repro/chained-delay-timing-repro.yml +13 -0
  171. package/test/configFiles/repro/count-init-repro.yml +45 -0
  172. package/test/configFiles/repro/cycle-break-repro.yml +44 -0
  173. package/test/configFiles/repro/debounce-repro.yml +46 -0
  174. package/test/configFiles/repro/diff-count-repro.yml +34 -0
  175. package/test/configFiles/repro/engine-hang-repro.yml +9 -0
  176. package/test/configFiles/repro/latch-apm-repro.yml +26 -0
  177. package/test/configFiles/repro/lockout-count-repro.yml +33 -0
  178. package/test/configFiles/repro/program-extract-repro.yml +38 -0
  179. package/test/configFiles/repro/state-latch-repro.yml +47 -0
  180. package/test/configFiles/repro/ternary-repro.yml +26 -0
  181. package/test/configFiles/transform/debounce.yml +12 -0
  182. package/test/configFiles/transform/expression.yml +34 -0
  183. package/test/configFiles/transform/ignoreValue.yml +31 -0
  184. package/test/configFiles/transform/latch.yml +11 -0
  185. package/test/configFiles/transform/latchValue.yml +31 -0
  186. package/test/configFiles/transform/logicAnd.yml +14 -0
  187. package/test/configFiles/transform/logicOr.yml +14 -0
  188. package/test/configFiles/transform/map.yml +19 -0
  189. package/test/configFiles/transform/maxLength.yml +13 -0
  190. package/test/configFiles/transform/offDelay.yml +12 -0
  191. package/test/configFiles/transform/pattern-escape.yml +10 -0
  192. package/test/configFiles/transform/pattern-match.yml +57 -0
  193. package/test/configFiles/transform/pattern-replace.yml +34 -0
  194. package/test/configFiles/transform/pattern-test.yml +25 -0
  195. package/test/configFiles/transform/reject.yml +24 -0
  196. package/test/configFiles/transform/risingEdgeCounter.yml +36 -0
  197. package/test/configFiles/transform/source.yml +20 -0
  198. package/test/configFiles/transform/state.yml +56 -0
  199. package/test/configFiles/transform/toggle.yml +19 -0
  200. package/test/configFiles/transform/whenUnavailable.yml +19 -0
  201. package/test/dataFiles/noisy-pulse.txt +11330 -0
  202. package/test/dataItems.test.js +140 -0
  203. package/test/engine-v1-tests.js +418 -0
  204. package/test/engine-v2-tests.js +284 -0
  205. package/test/expression-tests.js +171 -0
  206. package/test/expressionService.test.js +154 -0
  207. package/test/expressionServiceCondition.test.js +130 -0
  208. package/test/repro/buffer-convert-repro.test.js +38 -0
  209. package/test/repro/chained-delay-timing-repro.test.js +34 -0
  210. package/test/repro/count-init-repro.test.js +46 -0
  211. package/test/repro/cylce-break-repro.test.js +57 -0
  212. package/test/repro/debounce-repro.test.js +65 -0
  213. package/test/repro/diff-count-repro.test.js +79 -0
  214. package/test/repro/engine-hang-repro.test.js +38 -0
  215. package/test/repro/latch-apm-repro.test.js +119 -0
  216. package/test/repro/lockout-count-repro.test.js +84 -0
  217. package/test/repro/program-extract-repro.test.js +40 -0
  218. package/test/repro/state-latch-repro.test.js +63 -0
  219. package/test/repro/ternary-repro.test.js +43 -0
  220. package/test/transform/accumulte.test.js +18 -0
  221. package/test/transform/average.test.js +22 -0
  222. package/test/transform/debounce.test.js +70 -0
  223. package/test/transform/downsample.test.js +30 -0
  224. package/test/transform/edge.test.js +27 -0
  225. package/test/transform/expression.test.js +189 -0
  226. package/test/transform/fallingEdge.test.js +59 -0
  227. package/test/transform/fromBuffer.test.js +60 -0
  228. package/test/transform/hash.test.js +34 -0
  229. package/test/transform/ignoreValue.test.js +123 -0
  230. package/test/transform/invert.test.js +26 -0
  231. package/test/transform/latch.test.js +33 -0
  232. package/test/transform/latchValue.test.js +126 -0
  233. package/test/transform/logicAnd.test.js +80 -0
  234. package/test/transform/logicOr.test.js +80 -0
  235. package/test/transform/map.test.js +42 -0
  236. package/test/transform/max.test.js +30 -0
  237. package/test/transform/maxLength.test.js +32 -0
  238. package/test/transform/min.test.js +30 -0
  239. package/test/transform/minDelta.test.js +14 -0
  240. package/test/transform/offDelay.test.js +123 -0
  241. package/test/transform/onDelay.test.js +105 -0
  242. package/test/transform/patternEscape.test.js +18 -0
  243. package/test/transform/patternMatch.test.js +177 -0
  244. package/test/transform/patternReplace.test.js +95 -0
  245. package/test/transform/patternTest.test.js +105 -0
  246. package/test/transform/rateOfChange.test.js +34 -0
  247. package/test/transform/reject.test.js +56 -0
  248. package/test/transform/resample.test.js +193 -0
  249. package/test/transform/risingEdge.test.js +60 -0
  250. package/test/transform/risingEdgeCounter.test.js +227 -0
  251. package/test/transform/sampleInterval.test.js +22 -0
  252. package/test/transform/source.test.js +137 -0
  253. package/test/transform/state.test.js +248 -0
  254. package/test/transform/threshold.test.js +78 -0
  255. package/test/transform/toBuffer.test.js +60 -0
  256. package/test/transform/toggle.test.js +92 -0
  257. package/test/transform/trim.test.js +30 -0
  258. package/test/transform/valueChange.test.js +14 -0
  259. package/test/transform/valueDecrease.test.js +32 -0
  260. package/test/transform/valueIncrease.test.js +32 -0
  261. package/test/transform/valueIncreaseDiff.test.js +32 -0
  262. package/test/transform/whenUnavailable.test.js +93 -0
  263. package/test/transform/windowCount.test.js +26 -0
  264. package/test/util/testUtils.js +405 -0
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const TransformState = require('./transformState');
4
+
5
+ /**
6
+ * Emits a T signal each time the value increases in a stream.
7
+ */
8
+ class ValueIncreaseFilter extends TransformState {
9
+ constructor() {
10
+ super();
11
+
12
+ this.defaultValue = false;
13
+ this.value = false;
14
+ this.previousValue = false;
15
+ }
16
+
17
+ static op = 'value-increase';
18
+
19
+ static create(_args) {
20
+ return new ValueIncreaseFilter();
21
+ }
22
+
23
+ filter(context, value, time) {
24
+ this.commitValue(context, +value, time);
25
+ }
26
+
27
+ emitUpdate(context) {
28
+ if (this.forceEmit || (this.value > this.previousValue && this.valueTime === this.lastSampleTime)) {
29
+ this.forceEmit = false;
30
+ this.lastEmitValue = this.value;
31
+ this.lastEmitTime = this.lastSampleTime;
32
+ this.emit('update', true, this.lastSampleTime, context);
33
+ }
34
+ this.emit('update', false, this.lastSampleTime, context);
35
+ }
36
+ }
37
+
38
+ module.exports = ValueIncreaseFilter;
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const TransformState = require('./transformState');
4
+
5
+ /**
6
+ * Emits a diff value followed by a persistent 0 each time the input value increases
7
+ */
8
+ class ValueIncreaseDiffFilter extends TransformState {
9
+ constructor() {
10
+ super();
11
+
12
+ this.defaultValue = 0;
13
+ this.value = 0;
14
+ this.previousValue = 0;
15
+ this.ignoreFirst = true;
16
+ }
17
+
18
+ static op = 'value-increase-diff';
19
+
20
+ static create(_args) {
21
+ return new ValueIncreaseDiffFilter();
22
+ }
23
+
24
+ filter(context, value, time) {
25
+ value = +value;
26
+
27
+ if (this.ignoreFirst) {
28
+ this.ignoreFirst = false;
29
+ this.value = value;
30
+ this.previousValue = value;
31
+ this.valueTime = time;
32
+ this.previousValueTime = time;
33
+ return;
34
+ }
35
+
36
+ // Unconditionally swap over current to previous to avoid "multi-emitting" the diff
37
+ // event if multiple source updates trigger multiple executions of the transform chain
38
+ this.previousValue = this.value;
39
+ this.previousValueTime = this.valueTime;
40
+ this.value = value;
41
+ this.valueTime = time;
42
+ }
43
+
44
+ emitUpdate(context) {
45
+ const diff = this.value - this.previousValue;
46
+
47
+ if (this.forceEmit || this.valueTime === this.lastSampleTime) {
48
+ this.forceEmit = false;
49
+ this.lastEmitValue = this.value;
50
+ this.lastEmitTime = this.lastSampleTime;
51
+
52
+ if (diff > 0) {
53
+ this.emit('update', diff, this.lastSampleTime, context);
54
+ }
55
+ }
56
+
57
+ this.emit('update', 0, this.lastSampleTime, context);
58
+ }
59
+
60
+ setUnavailable(context, time) {
61
+ super.setUnavailable(context, time);
62
+ this.ignoreFirst = true;
63
+ }
64
+ }
65
+
66
+ module.exports = ValueIncreaseDiffFilter;
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const TransformState = require('./transformState');
4
+
5
+ /**
6
+ * Checks incoming value for special UNAVAILABLE state
7
+ *
8
+ * When the incoming value is UNAVAILABLE (exact match including case), the corresponding expression
9
+ * is executed for the result value. The expression itself may result in UNAVAILABLE. When the
10
+ * incoming value is anything else, it is passed through.
11
+ */
12
+ class WhenUnavailableFilter extends TransformState {
13
+ constructor({ engine, path, args: { compiledExpression } }) {
14
+ super();
15
+
16
+ this.engine = engine;
17
+ this.compiledExpression = compiledExpression;
18
+ this.selfSources = engine.expressionService.expressionTriggers(path);
19
+ }
20
+
21
+ static op = 'when-unavailable';
22
+
23
+ static create(args) {
24
+ return new WhenUnavailableFilter(args);
25
+ }
26
+
27
+ static parseConfig(configUtil, defn) {
28
+ const expression = (defn.args[defn.func] ?? '').toString();
29
+ const compiledExpression = configUtil.compileExpression(expression, this.op);
30
+
31
+ defn.args = { expression, compiledExpression };
32
+ }
33
+
34
+ static getExpressions(configUtil, body, info) {
35
+ return configUtil.getExpressions(body, info);
36
+ }
37
+
38
+ setUnavailable(context, time) {
39
+ this.update(context, 'UNAVAILABLE', time);
40
+ }
41
+
42
+ filter(context, value, time) {
43
+ if (value !== 'UNAVAILABLE') {
44
+ this.commitValue(context, value, time);
45
+ return;
46
+ }
47
+
48
+ const localState = {};
49
+ const unavailTerm = this.selfSources.reduce((res, name) => {
50
+ const state = this.engine.getState(name);
51
+ localState[name] = state?.value;
52
+ return res || !state || !state.available;
53
+ }, false);
54
+
55
+ if (unavailTerm) {
56
+ super.setUnavailable(context, time);
57
+ return;
58
+ }
59
+
60
+ try {
61
+ if (!this.origin) {
62
+ localState.this = value;
63
+ }
64
+ this.commitValue(context, this.compiledExpression.evaluate(localState), time);
65
+ } catch (err) {
66
+ // Probably a type error in one of the arguments.
67
+ this.recordError(context, err, time);
68
+ super.setUnavailable(context, time);
69
+ }
70
+ }
71
+ }
72
+
73
+ module.exports = WhenUnavailableFilter;
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const TransformState = require('./transformState');
5
+
6
+ /**
7
+ * Keeps a count of all rising edges (F->T) that appear in a given sliding window.
8
+ *
9
+ * For every incoming sample, the internal counter value will be emitted. When a new rising edge
10
+ * is found, the counter will be incremented. When a previously seen rising edge is older than the
11
+ * configured window length, the counter will be decremented.
12
+ */
13
+ class WindowCountFilter extends TransformState {
14
+ constructor(window) {
15
+ super();
16
+
17
+ this.defaultValue = false;
18
+ this.window = window;
19
+ this.initReset();
20
+ }
21
+
22
+ static op = 'window-count';
23
+
24
+ static create(args) {
25
+ return new WindowCountFilter(args.args.window);
26
+ }
27
+
28
+ static parseConfig(configUtil, defn) {
29
+ const { value: window, usingShorthand } = configUtil.getArgOrShorthand(defn, 'window');
30
+ configUtil.requirePositive(window, usingShorthand ? this.op : 'window', this.op);
31
+
32
+ defn.args = { window };
33
+ }
34
+
35
+ initReset() {
36
+ this.value = false;
37
+ this.previousValue = false;
38
+ this.countHistory = [];
39
+ }
40
+
41
+ update(context, value, time) {
42
+ this.lastSampleTime = time;
43
+ if (!this.available) {
44
+ this.available = true;
45
+
46
+ // Don't count when becoming available already in a high state
47
+ this.emitUpdate(context);
48
+ return;
49
+ }
50
+
51
+ this.filter(context, value, time);
52
+ this.emitUpdate(context);
53
+ }
54
+
55
+ emitUpdate(context) {
56
+ if (this.countHistory.length > 0 && this.countHistory[0] <= this.lastSampleTime - this.window) {
57
+ const [expired, current] = _.partition(this.countHistory, t => t < this.lastSampleTime - this.window);
58
+ this.countHistory = current;
59
+
60
+ // Go through all recorded events older than sliding window, and emit new counter values at the times
61
+ // they should have exactly expired.
62
+ _.reduce(expired, (count, t) => {
63
+ this.emit('update', count - 1, t + this.window, context);
64
+ return count - 1;
65
+ }, current.length + expired.length);
66
+
67
+ // An event that expired at exactly lastSampleTime is handled separately from above, and will be
68
+ // accounted for in the normal emit.
69
+ if (this.countHistory.length > 0 && this.countHistory[0] === this.lastSampleTime - this.window) {
70
+ this.countHistory.shift();
71
+ }
72
+ }
73
+
74
+ if (this.value && this.valueTime === this.lastSampleTime) {
75
+ this.countHistory.push(this.lastSampleTime);
76
+ }
77
+ this.emit('update', this.countHistory.length, this.lastSampleTime, context);
78
+ }
79
+
80
+ setUnavailable(context, time) {
81
+ super.setUnavailable(context, time);
82
+ this.initReset();
83
+ }
84
+ }
85
+
86
+ module.exports = WindowCountFilter;
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ module.exports = {
7
+ mkdirSync: (targetDir, { isRelativeToScript = false } = {}) => {
8
+ const initDir = path.isAbsolute(targetDir) ? path.sep : '';
9
+ const baseDir = isRelativeToScript ? __dirname : '.';
10
+
11
+ targetDir.split(path.sep).reduce((parentDir, childDir) => {
12
+ const curDir = path.resolve(baseDir, parentDir, childDir);
13
+ try {
14
+ fs.mkdirSync(curDir);
15
+ } catch (err) {
16
+ if (err.code === 'EEXIST' || err.code === 'EISDIR') {
17
+ return curDir;
18
+ }
19
+ if (err.code === 'EPERM') {
20
+ if (fs.existsSync(curDir)) {
21
+ return curDir;
22
+ }
23
+ }
24
+
25
+ throw err;
26
+ }
27
+
28
+ return curDir;
29
+ }, initDir);
30
+ },
31
+
32
+ ensureFileDirectoryExistence: (filePath) => {
33
+ const dirname = path.dirname(filePath);
34
+ return module.exports.ensureDirectoryExistence(dirname);
35
+ },
36
+
37
+ ensureDirectoryExistence: (dirPath) => {
38
+ if (fs.existsSync(dirPath)) {
39
+ return;
40
+ }
41
+
42
+ fs.mkdirSync(dirPath, { recursive: true });
43
+ },
44
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@machinemetrics/io-adapter-lib",
3
+ "version": "2.32.0",
4
+ "description": "Configuration and engine implementation for MachineMetrics AdapterScripts and adapters",
5
+ "main": "index.js",
6
+ "license": "UNLICENSED",
7
+ "author": {
8
+ "name": "MachineMetrics",
9
+ "url": "http://machinemetrics.com"
10
+ },
11
+ "dependencies": {
12
+ "buffer": "^6.0.3",
13
+ "cronosjs": "^1.7.1",
14
+ "eventemitter3": "^5.0.1",
15
+ "lodash": "^4.17.21",
16
+ "mathjs": "^14.2.1",
17
+ "md5": "^2.3.0",
18
+ "sha1": "^1.1.1",
19
+ "yaml": "^1.10.0"
20
+ },
21
+ "engines": {
22
+ "node": ">=20.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "chai": "^4.5.0",
26
+ "eslint": "^8.57.1",
27
+ "eslint-config-airbnb": "^19.0.2",
28
+ "eslint-plugin-import": "^2.25.3",
29
+ "eslint-plugin-jsx-a11y": "^6.5.1",
30
+ "eslint-plugin-react": "^7.27.1",
31
+ "mocha": "^11.1.0"
32
+ },
33
+ "scripts": {
34
+ "test": "npm run lint && npm run test-only",
35
+ "test-only": "mocha test/*",
36
+ "lint": "eslint ."
37
+ }
38
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "parserOptions": {
3
+ "ecmaVersion": 2022
4
+ },
5
+ "rules": {
6
+ "import/no-extraneous-dependencies": [ "off" ],
7
+ "prefer-arrow-callback": "off"
8
+ },
9
+ "env": {
10
+ "mocha": true
11
+ },
12
+ "globals": {
13
+ "BigInt": true
14
+ }
15
+ }
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const Transform = require('../lib/transform');
4
+ const testUtils = require('./util/testUtils');
5
+
6
+ describe('Chained transform tests', function () {
7
+ // NB: on/off delays are not commutative, though it can appear that they are in some tests.
8
+ it('on-delay + off-delay', function () {
9
+ const filter = new Transform.onDelay(2)
10
+ .chain(new Transform.offDelay(2));
11
+ testUtils.attachTransformValidator(filter);
12
+
13
+ filter.testDigital('__^^__^^^^____^_^^^^^_', 1);
14
+ // '____|___^^________^^^_'
15
+ filter.valdDigital('____^^__^^^^______^^^^', 1);
16
+ });
17
+
18
+ it('off-delay + on-delay', function () {
19
+ const filter = new Transform.offDelay(2)
20
+ .chain(new Transform.onDelay(2));
21
+ testUtils.attachTransformValidator(filter);
22
+
23
+ filter.testDigital('__^^__^^^^____^_^^^^^_', 1);
24
+ // '__^^^^|^^^^^__^^^^^^^^'
25
+ filter.valdDigital('____^^__^^^^____^^^^^^', 1);
26
+ });
27
+
28
+ it('rising-edge + off-delay (on-latch)', function () {
29
+ const filter = new Transform.risingEdge()
30
+ .chain(new Transform.offDelay(2));
31
+ testUtils.attachTransformValidator(filter);
32
+
33
+ filter.testDigital('__^^__^^^^____^_^^^^^_', 1);
34
+ // '__|___|_______|_|_____'
35
+ filter.valdDigital('__^^__^^______^^|^____', 1);
36
+ });
37
+
38
+ it('falling-edge + off-delay + invert (off-latch)', function () {
39
+ const filter = new Transform.fallingEdge()
40
+ .chain(new Transform.offDelay(2))
41
+ .chain(new Transform.invert());
42
+ testUtils.attachTransformValidator(filter);
43
+
44
+ filter.testDigital('__^^__^^^^____^_^^^^^_', 1);
45
+ // '____|_____|____|_____|'
46
+ // '____^^____^^___^^____^'
47
+ filter.valdDigital('^^^^__^^^^__^^^__^^^^_', 1);
48
+ });
49
+
50
+ // NB: In theory the same as above, except start-of-stream conditions lead to difference at start
51
+ it('falling-edge + invert + on-delay (off-latch)', function () {
52
+ const filter = new Transform.fallingEdge()
53
+ .chain(new Transform.invert())
54
+ .chain(new Transform.onDelay(2));
55
+ testUtils.attachTransformValidator(filter);
56
+
57
+ filter.testDigital('__^^__^^^^____^_^^^^^_', 1);
58
+ // '____|_____|____|_____|'
59
+ // '^^^^|^^^^^|^^^^|^^^^^|'
60
+ filter.valdDigital('__^^__^^^^__^^^__^^^^_', 1);
61
+ });
62
+
63
+ it('rising-edge + off-delay + rising-edge-counter (counter cooldown)', function () {
64
+ const filter = new Transform.risingEdge()
65
+ .chain(new Transform.offDelay(3))
66
+ .chain(new Transform.risingEdgeCounter(testUtils.standardBuilderArgs(null, 'test1')));
67
+ testUtils.attachTransformValidator(filter);
68
+
69
+ filter.testDigital('__^^__^^^^____^_^^^^^_^_', 1);
70
+ // '__|___|_______|_|_____|_'
71
+ // '__^^^_^^^_____^^^^^___^^'
72
+ filter.valdCounter(' + + + + ', 1);
73
+ });
74
+
75
+ it('rising-edge + off-delay + falling-edge + off-delay', function () {
76
+ const filter = new Transform.risingEdge()
77
+ .chain(new Transform.offDelay(1))
78
+ .chain(new Transform.fallingEdge())
79
+ .chain(new Transform.offDelay(1));
80
+ testUtils.attachTransformValidator(filter);
81
+
82
+ filter.testDigital('__^^^^______^____', 1);
83
+ // '__|_________|____'
84
+ // '__^_________^____'
85
+ // '___|_________|___'
86
+ filter.valdDigital('___^_________^___', 1);
87
+ });
88
+ });
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ /* eslint-disable no-template-curly-in-string */
4
+ /* eslint-disable no-useless-escape */
5
+
6
+ const expect = require('chai').expect;
7
+ const EngineV2 = require('../lib/engine/engineV2');
8
+ const Builder = require('../lib/engine/transformBuilderV2');
9
+ const DataOutput = require('../lib/engine/dataOutput');
10
+ const testUtils = require('./util/testUtils');
11
+
12
+ describe('data items', async function () {
13
+ let config;
14
+ before(async () => {
15
+ config = await testUtils.loadConfig('conditions.yml');
16
+ });
17
+
18
+ it('parses overrides', async function () {
19
+ expect(config.adapter.conditions.cond3.passthroughSource).to.eq('key4');
20
+ expect(config.adapter.conditions.cond3.overrides.length).to.eq(3);
21
+
22
+ expect(config.adapter.conditions.cond3.overrides[0].code).to.eq('E${this}');
23
+ expect(config.adapter.conditions.cond3.overrides[0].codeIsStatic).to.eq(false);
24
+ expect(config.adapter.conditions.cond3.overrides[0].codePath).to.eq('conditions.cond3.overrides.0.override-code');
25
+ expect(config.adapter.conditions.cond3.overrides[0].matchCode.toString()).to.eq(/^X\d+$/.toString());
26
+ expect(config.adapter.conditions.cond3.overrides[0].codeResolverTriggers).to.deep.eq([]);
27
+
28
+ expect(config.adapter.conditions.cond3.overrides[0].message).to.eq('Extra ${this}');
29
+ expect(config.adapter.conditions.cond3.overrides[0].messageIsStatic).to.eq(false);
30
+ expect(config.adapter.conditions.cond3.overrides[0].messagePath).to.eq('conditions.cond3.overrides.0.override-message');
31
+ expect(config.adapter.conditions.cond3.overrides[0].messageResolverTriggers).to.deep.eq([]);
32
+
33
+ expect(config.adapter.conditions.cond3.overrides[1].message).to.eq('${this} coolant=${key1}');
34
+ expect(config.adapter.conditions.cond3.overrides[1].messageIsStatic).to.eq(false);
35
+ expect(config.adapter.conditions.cond3.overrides[1].messagePath).to.eq('conditions.cond3.overrides.1.override-message');
36
+ expect(config.adapter.conditions.cond3.overrides[1].matchMessage.toString()).to.eq(/^.*coolant.*$/.toString());
37
+ expect(config.adapter.conditions.cond3.overrides[1].messageResolverTriggers).to.deep.eq(['key1']);
38
+
39
+ expect(config.adapter.conditions.cond3.overrides[2].code).to.eq('Y');
40
+ expect(config.adapter.conditions.cond3.overrides[2].codeIsStatic).to.eq(true);
41
+ expect(config.adapter.conditions.cond3.overrides[2].codePath).to.eq('conditions.cond3.overrides.2.override-code');
42
+ expect(config.adapter.conditions.cond3.overrides[2].codeResolverTriggers).to.deep.eq([]);
43
+ expect(config.adapter.conditions.cond3.overrides[2].message).to.eq('Z');
44
+ expect(config.adapter.conditions.cond3.overrides[2].messageIsStatic).to.eq(true);
45
+ expect(config.adapter.conditions.cond3.overrides[2].messagePath).to.eq('conditions.cond3.overrides.2.override-message');
46
+ expect(config.adapter.conditions.cond3.overrides[2].messageResolverTriggers).to.deep.eq([]);
47
+ });
48
+
49
+ it('captures simple data items', async function () {
50
+ const source = testUtils.valueSource();
51
+ const [dataOutput, captured] = makeDataOutput(config, source);
52
+
53
+ source.sendValue('key1', 7, 2);
54
+ dataOutput.handleScanSync();
55
+ source.sendValue('key1', 11, 3);
56
+ dataOutput.handleScanSync();
57
+ source.sendValue('key2', 2, 4);
58
+ dataOutput.handleScanSync();
59
+ source.sendValue('key2', 5, 5);
60
+ dataOutput.handleScanSync();
61
+ source.sendValue('key3', 33, 6);
62
+ dataOutput.handleScanSync();
63
+
64
+ expect(captured.length).to.eq(12);
65
+ validateCondition(captured[3], 'cond1', 'WARNING', 2000, 'A1', 'Code A1');
66
+ validateUnavail(captured[4], 'cond1', 2000, 'A2');
67
+ validateCondition(captured[5], 'cond1', 'FAULT', 3000, 'A1', 'Code A1');
68
+ validateUnavail(captured[6], 'cond1', 3000, 'A2');
69
+ validateCondition(captured[7], 'cond1', 'FAULT', 4000, 'A1', 'Code A1');
70
+ validateCondition(captured[8], 'cond1', 'NORMAL', 4000, 'A2', 'Code A2');
71
+ validateCondition(captured[9], 'cond1', 'FAULT', 5000, 'A1', 'Code A1');
72
+ validateCondition(captured[10], 'cond1', 'FAULT', 5000, 'A2', 'Code A2');
73
+ validateCondition(captured[11], 'cond2', 'FAULT', 6000, 'B33', 'Code B33');
74
+ });
75
+ });
76
+
77
+ function validateCondition(entry, key, value, time, code, message) {
78
+ expect(entry.event).to.eq('data');
79
+ expect(entry.key).to.eq(key);
80
+ expect(entry.value).to.eq(value);
81
+ expect(entry.time).to.eq(time);
82
+ expect(entry.meta.type).to.eq('condition');
83
+ expect(entry.meta.nativeCode).to.eq(code);
84
+ expect(entry.meta.message).to.eq(message);
85
+ }
86
+
87
+ function validateUnavail(entry, key, time, code) {
88
+ expect(entry.event).to.eq('unavailable');
89
+ expect(entry.key).to.eq(key);
90
+ expect(entry.time).to.eq(time);
91
+ if (code) {
92
+ expect(entry.meta.type).to.eq('condition');
93
+ expect(entry.meta.nativeCode).to.eq(code);
94
+ }
95
+ }
96
+
97
+ function makeDataOutput(config, source) {
98
+ const engine = new EngineV2(config);
99
+ const builder = new Builder(config);
100
+ builder.build(engine);
101
+
102
+ const buf = [];
103
+ const dataOutput = new DataOutput(engine, config);
104
+ dataOutput.on('data', (key, value, time, meta) => {
105
+ buf.push({ event: 'data', key, value, time, meta });
106
+ });
107
+ dataOutput.on('data_unavailable', (key, time, meta) => {
108
+ buf.push({ event: 'unavailable', key, time, meta });
109
+ });
110
+
111
+ engine.on('update', dataOutput.handleUpdate.bind(dataOutput));
112
+ engine.on('unavailable', dataOutput.handleItemUnavailable.bind(dataOutput));
113
+ engine.addValueSource(source);
114
+
115
+ dataOutput.initiailize();
116
+
117
+ return [dataOutput, buf];
118
+ }
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const expect = require('chai').expect;
4
+ const testUtils = require('../util/testUtils');
5
+
6
+ describe('Allen Bradley PCCC config tests', function () {
7
+ let config;
8
+ let config2;
9
+ before(async () => {
10
+ config = await testUtils.loadConfig('device/ab-pccc-default.yml');
11
+ config2 = await testUtils.loadConfig('device/ab-pccc-full.yml');
12
+ });
13
+
14
+ it('reads default config', async function () {
15
+ expect(config.device.host).to.eq('192.168.1.150');
16
+ expect(config.device.port).to.eq(undefined);
17
+ expect(config.device.scanRate).to.eq(1);
18
+ expect(config.device.routing).to.eq(false);
19
+ expect(config.device.verboseDevice).to.eq(false);
20
+ expect(config.device.extraVerboseDevice).to.eq(false);
21
+ expect(config.device.deviceTimeout).to.eq(4.5);
22
+ expect(config.device.slot).to.eq(0);
23
+ expect(config.device.tags).to.deep.eq({});
24
+ });
25
+
26
+ it('reads full config', async function () {
27
+ expect(config2.device.host).to.eq('192.168.1.150');
28
+ expect(config2.device.port).to.eq(1234);
29
+ expect(config2.device.scanRate).to.eq(0.1);
30
+ expect(config2.device.routing).to.eq(true);
31
+ expect(config2.device.verboseDevice).to.eq(true);
32
+ expect(config2.device.extraVerboseDevice).to.eq(true);
33
+ expect(config2.device.deviceTimeout).to.eq(10);
34
+ expect(config2.device.slot).to.eq(1);
35
+
36
+ expect(Object.keys(config2.device.tags).length).to.eq(3);
37
+ expect(config2.device.tags.tag1).to.deep.eq({ name: 'tag1', path: 'F7:5' });
38
+ expect(config2.device.tags.tag2).to.deep.eq({ name: 'tag2', path: 'B12:3/5' });
39
+ expect(config2.device.tags.tag3).to.deep.eq({ name: 'tag3', path: 'ST3:1,4' });
40
+ });
41
+ });
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const expect = require('chai').expect;
4
+ const testUtils = require('../util/testUtils');
5
+
6
+ describe('ADAM-6052 config tests', function () {
7
+ let defaultConfig;
8
+ before(async () => {
9
+ defaultConfig = await testUtils.loadConfig('device/adam-6052-default.yml');
10
+ });
11
+
12
+ it('reads default config', async function () {
13
+ expect(defaultConfig.device.host).to.eq('192.168.1.100');
14
+ expect(defaultConfig.device.pins).to.deep.eq({});
15
+ });
16
+ });