@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,106 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('./configError');
5
+ const TransformConfigUtil = require('./transformConfigUtil');
6
+
7
+ class EngineConfig {
8
+ constructor(expressionService, config = {}, device = {}) {
9
+ this.expressionService = expressionService;
10
+ this.config = config;
11
+ this.device = device;
12
+
13
+ this.configUtil = new TransformConfigUtil(expressionService);
14
+
15
+ _.each(this.getIdentifiers(this.config), (ident) => {
16
+ if (!expressionService.isNameValid(ident.name)) {
17
+ throw new ConfigError(`variable name '${ident.name}' is not a valid name`).atSection('variables').atAttribute(ident.name);
18
+ } else if (expressionService.isNameDefined(ident.name)) {
19
+ throw new ConfigError(`variable name '${ident.name}' already defined in earlier section`).atSection('variables').atAttribute(ident.name);
20
+ }
21
+
22
+ expressionService.addName(ident);
23
+ });
24
+
25
+ // Standard identifier for devices to indicate whether they are fully connected or not
26
+ expressionService.addName({
27
+ name: 'device-connected',
28
+ channel: 'system',
29
+ defaultValue: false,
30
+ });
31
+
32
+ // Remap source shorthand format, ex:
33
+ // variables:
34
+ // path1: input1 + 5
35
+ // part1: composite.parts
36
+ //
37
+ // to:
38
+ // variables:
39
+ // path1:
40
+ // - source: input1 + 5
41
+ // part1:
42
+ // - source: composite.parts
43
+
44
+ this.config.variables = _.mapValues(this.config.variables, (variable) => {
45
+ if (_.isString(variable)) {
46
+ return [{ source: variable }];
47
+ }
48
+ return variable;
49
+ });
50
+
51
+ _.each(this.getExpressions(this.config), name => expressionService.addExpression(name));
52
+ _.each(this.getStringExpressions(this.config), name => expressionService.addStringExpression(name));
53
+ }
54
+
55
+ getIdentifiers(config = {}) {
56
+ if (!config.variables) {
57
+ return [];
58
+ }
59
+
60
+ return _.map(_.keys(config.variables), (name) => {
61
+ return {
62
+ name,
63
+ channel: 'engine',
64
+ defaultValue: 0, // TODO: Should figure out from final listed transform
65
+ };
66
+ });
67
+ }
68
+
69
+ getStringExpressions(config = {}) {
70
+ if (!config.variables) {
71
+ return [];
72
+ }
73
+
74
+ return this.configUtil.getStringExpressionsFromVariables(config.variables);
75
+ }
76
+
77
+ getExpressions(config = {}) {
78
+ if (!config.variables) {
79
+ return [];
80
+ }
81
+
82
+ return this.configUtil.getExpressionsFromVariables(config.variables);
83
+ }
84
+
85
+ parse(config = {}) {
86
+ this.scanInterval = config['scan-interval'] || 0.002;
87
+
88
+ this.variableNames = [];
89
+
90
+ this.variables = this.loadVariables(config);
91
+ }
92
+
93
+ loadVariables(defn) {
94
+ if (!defn.variables) {
95
+ return {};
96
+ }
97
+
98
+ this.variableNames = _.keys(defn.variables);
99
+
100
+ return _.mapValues(defn.variables, (variable, name) => {
101
+ return this.configUtil.loadTransformList(variable, `variables.${name}`, true);
102
+ });
103
+ }
104
+ }
105
+
106
+ module.exports = EngineConfig;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const validations = require('../common/validations');
4
+
5
+ class CounterGenConfig {
6
+ generatorName() {
7
+ return 'counter';
8
+ }
9
+
10
+ parse(config = {}) {
11
+ this.interval = validations.checkInterval(config, 'interval', 1);
12
+ }
13
+ }
14
+
15
+ module.exports = CounterGenConfig;
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const cronosjs = require('cronosjs');
5
+ const ConfigError = require('../configError');
6
+ const validations = require('../common/validations');
7
+
8
+ class CronGenConfig {
9
+ generatorName() {
10
+ return 'cron';
11
+ }
12
+
13
+ parse(config = {}) {
14
+ this.cron = this.checkCron(config);
15
+ this.duration = validations.checkInterval(config, 'duration', 1);
16
+ this.timezone = validations.checkTimezone(config, 'timezone');
17
+ }
18
+
19
+ checkCron(defn) {
20
+ const cron = defn.cron;
21
+ if (_.isEmpty(cron)) {
22
+ throw new ConfigError('A valid cron expression must be specified').atAttribute('cron');
23
+ }
24
+
25
+ if (!cronosjs.validate(cron)) {
26
+ throw new ConfigError('Invalid or unsupported cron expression').atAttribute('cron');
27
+ }
28
+
29
+ return cron;
30
+ }
31
+ }
32
+
33
+ module.exports = CronGenConfig;
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('../configError');
5
+ const validations = require('../common/validations');
6
+
7
+ class DateTimeGenConfig {
8
+ generatorName() {
9
+ return 'datetime';
10
+ }
11
+
12
+ parse(config = {}) {
13
+ this.format = this.checkDateFormat(config);
14
+ this.interval = validations.checkInterval(config, 'interval', 1);
15
+ this.utc = this.checkUTC(config);
16
+ this.timezone = validations.checkTimezone(config, 'timezone');
17
+ }
18
+
19
+ checkDateFormat(defn) {
20
+ const dateFormat = defn.format ?? 'YYYY-MM-DDTHH:mm:ss';
21
+ if (_.isEmpty(dateFormat)) {
22
+ throw new ConfigError('A date format must be specified').atPath('format');
23
+ }
24
+
25
+ return dateFormat;
26
+ }
27
+
28
+ checkUTC(defn) {
29
+ return !!(defn.utc ?? true);
30
+ }
31
+ }
32
+
33
+ module.exports = DateTimeGenConfig;
@@ -0,0 +1,339 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const yaml = require('yaml');
5
+ const ConfigError = require('./configError');
6
+ const EngineConfigV1 = require('./engineConfigV1');
7
+ const EngineConfigV2 = require('./engineConfigV2');
8
+ const AdapterConfig = require('./adapterConfig');
9
+ const LabjackU3Config = require('./device/labjackU3Config');
10
+ const LabjackT4Config = require('./device/labjackT4Config');
11
+ const LabjackT7Config = require('./device/labjackT7Config');
12
+ const OPCUAConfig = require('./device/opcuaConfig');
13
+ const AdamConfig = require('./device/adam-6052Config');
14
+ const MTConnectConfig = require('./device/mtconnectConfig');
15
+ const MTConnectHaasConfig = require('./device/mtconnectHaasConfig');
16
+ const MTConnectAdapterConfig = require('./device/mtconnectAdapterConfig');
17
+ const ModbusTcpConfig = require('./device/modbusTcpConfig');
18
+ const EthernetIPConfig = require('./device/ethernetIPConfig');
19
+ const PcccConfig = require('./device/pcccConfig');
20
+ const ExpressionService = require('../expressionService');
21
+ const BrotherHTTPConfig = require('./device/brotherHTTPConfig');
22
+ const MqttConfig = require('./device/mqttConfig');
23
+ const MqttLincolnConfig = require('./device/mqttLincolnConfig');
24
+ const NullConfig = require('./device/nullConfig');
25
+ const IfmIotConfig = require('./device/ifmIotConfig');
26
+ const JsonHttpConfig = require('./device/jsonHttpConfig');
27
+ const DateTimeGenConfig = require('./generator/dateTimeGenConfig');
28
+ const CounterGenConfig = require('./generator/counterGenConfig');
29
+ const CronGenConfig = require('./generator/cronGenConfig');
30
+
31
+ class Config {
32
+ constructor(configJson, options = {}) {
33
+ this.json = configJson;
34
+
35
+ this.document = options.document;
36
+
37
+ this.skipValidate = configJson['skip-validate'] ?? false;
38
+ this.port = configJson['mtconnect-port'];
39
+
40
+ this.diagnosticPort = configJson['diagnostic-port'];
41
+
42
+ this.deviceName = this.checkDevice(configJson);
43
+ this.configVersion = this.checkVersion(configJson);
44
+ this.engineVersion = this.checkEngineVersion(configJson);
45
+ this.replayEndTime = this.checkReplayCollection(configJson);
46
+
47
+ this.machineId = configJson.id;
48
+ this.machineRef = configJson['machine-ref'];
49
+ this.sourceRef = configJson['source-id'];
50
+
51
+ this.expressionService = new ExpressionService(configJson);
52
+
53
+ this.device = this.protectedCall('createDeviceConfig', () => {
54
+ return this.getDeviceConfig(this.deviceName, this.expressionService);
55
+ });
56
+
57
+ this.generators = this.protectedCall('createGeneratorConfig', () => {
58
+ return _.mapValues(configJson.generators, (generator, name) => {
59
+ this.expressionService.addName({
60
+ name,
61
+ channel: 'generator',
62
+ defaultValue: 0,
63
+ });
64
+ return this.getGeneratorConfig(generator.type);
65
+ });
66
+ });
67
+
68
+ this.engine = this.protectedCall('createEngineConfig', () => {
69
+ if (this.configVersion === 1) {
70
+ return new EngineConfigV1(this.expressionService);
71
+ } else if (this.configVersion === 2) {
72
+ return new EngineConfigV2(this.expressionService, this.json, this.device);
73
+ }
74
+ return null;
75
+ });
76
+
77
+ this.adapter = this.protectedCall('createAdapterConfig', () => {
78
+ return new AdapterConfig(this.expressionService, this.engine);
79
+ });
80
+
81
+ this.protectedCall('parseDevice', this.device.parse.bind(this.device, configJson));
82
+
83
+ _.each(this.generators, (generator, name) => {
84
+ this.protectedCall('parseGenerator', generator.parse.bind(generator, configJson.generators[name]));
85
+ });
86
+
87
+ this.protectedCall('parseEngine', this.engine.parse.bind(this.engine, configJson));
88
+ this.protectedCall('parseAdapter', this.adapter.parse.bind(this.adapter, configJson));
89
+
90
+ if (this.engine.postParse) {
91
+ this.protectedCall('postParseEngine', this.engine.postParse.bind(this.engine, this));
92
+ }
93
+ if (this.device.postParse) {
94
+ this.protectedCall('postParseDevice', this.device.postParse.bind(this.device, this));
95
+ }
96
+ }
97
+
98
+ protectedCall(sectionName, fn) {
99
+ try {
100
+ return fn();
101
+ } catch (err) {
102
+ err.extra = this.getErrorContext({
103
+ ...err?.extra,
104
+ section: sectionName,
105
+ });
106
+ throw err;
107
+ }
108
+ }
109
+
110
+ getErrorContext(context = {}) {
111
+ return {
112
+ machineId: this.machineId,
113
+ machineRef: this.machineRef,
114
+ sourceRef: this.sourceRef,
115
+ mtconnectPort: this.port,
116
+ diagnosticPort: this.diagnosticPort,
117
+ deviceName: this.deviceName,
118
+ configVersion: this.configVersion,
119
+ skipValidate: this.skipValidate,
120
+ ...context,
121
+ };
122
+ }
123
+
124
+ static async loadFile(filepath, options = {}) {
125
+ // eslint-disable-next-line global-require
126
+ const fs = require('fs/promises');
127
+
128
+ const file = await fs.readFile(filepath, 'utf8');
129
+ return this.load(file, options);
130
+ }
131
+
132
+ static load(data, options = {}) {
133
+ const cst = yaml.parseCST(data);
134
+ const document = new yaml.Document({ keepCstNodes: true }).parse(cst[0]);
135
+ if (_.size(document.errors) > 0) {
136
+ const error = _.first(document.errors);
137
+ const startPos = error.source.rangeAsLinePos.start;
138
+ const endPos = error.source.rangeAsLinePos.end;
139
+ let lineMessage = `line ${startPos.line}`;
140
+ if (startPos.line !== endPos.line) {
141
+ lineMessage = `lines ${startPos.line} - ${endPos.line}`;
142
+ }
143
+ throw new ConfigError(`YAML Error (${lineMessage}): ${error.message}`);
144
+ }
145
+
146
+ // Caller may wish to inject additional properties that are not part of the YAML document
147
+ if (!_.isEmpty(options.inject)) {
148
+ let inject = options.inject;
149
+ if (_.isString(inject)) {
150
+ try {
151
+ inject = yaml.parse(inject);
152
+ } catch (err) {
153
+ throw new ConfigError(`Injection Error: ${err.message}`);
154
+ }
155
+ }
156
+ if (!_.isObject(inject) || _.isArray(inject)) {
157
+ throw new ConfigError('Injection Error: Expected object');
158
+ }
159
+
160
+ _.each(inject, (val, key) => {
161
+ if (!document.get(key)) {
162
+ document.set(key, val);
163
+ }
164
+ });
165
+ }
166
+
167
+ const parsed = document.toJSON();
168
+
169
+ return new Config(parsed, { ...options, document });
170
+ }
171
+
172
+ dumpYaml() {
173
+ if (this.document) {
174
+ return this.document.toString();
175
+ }
176
+
177
+ const document = new yaml.Document();
178
+ document.contents = this.json;
179
+ return document.toString();
180
+ }
181
+
182
+ getGeneratorConfig(genName) {
183
+ switch (genName) {
184
+ case 'datetime':
185
+ return new DateTimeGenConfig();
186
+ case 'counter':
187
+ return new CounterGenConfig();
188
+ case 'cron':
189
+ return new CronGenConfig();
190
+ default:
191
+ throw new ConfigError(`Generator ${genName} not implemented`);
192
+ }
193
+ }
194
+
195
+ getDeviceConfig(deviceName, expressionService) {
196
+ switch (deviceName) {
197
+ case 'labjack-u3':
198
+ return new LabjackU3Config(expressionService);
199
+ case 'labjack-t4':
200
+ return new LabjackT4Config(expressionService);
201
+ case 'labjack-t7':
202
+ return new LabjackT7Config(expressionService);
203
+ case 'opc-ua':
204
+ return new OPCUAConfig(expressionService);
205
+ case 'brother':
206
+ case 'brother-http':
207
+ return new BrotherHTTPConfig(expressionService);
208
+ case 'adam-6052':
209
+ return new AdamConfig(expressionService);
210
+ case 'mtconnect':
211
+ return new MTConnectConfig(expressionService);
212
+ case 'mtconnect-haas':
213
+ return new MTConnectHaasConfig(expressionService);
214
+ case 'mtconnect-adapter':
215
+ return new MTConnectAdapterConfig(expressionService);
216
+ case 'modbus-tcp':
217
+ return new ModbusTcpConfig(expressionService);
218
+ case 'ethernet-ip':
219
+ return new EthernetIPConfig(expressionService);
220
+ case 'ab-pccc':
221
+ return new PcccConfig(expressionService);
222
+ case 'mqtt':
223
+ return new MqttConfig(expressionService);
224
+ case 'mqtt-lincoln':
225
+ return new MqttLincolnConfig(expressionService);
226
+ case 'iolink-ifm-iot':
227
+ return new IfmIotConfig(expressionService);
228
+ case 'json-http':
229
+ return new JsonHttpConfig(expressionService);
230
+ case 'local':
231
+ return new NullConfig(expressionService);
232
+ default:
233
+ throw new ConfigError(`Device ${this.device} not implemented`);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Checks 'version' top-level attribute
239
+ *
240
+ * Optional. Specifies the major version of the config language. Additive changes to the language, such as new
241
+ * attributes, would not warrant introducing a new config version. Defaults to the deprecated version 1.
242
+ */
243
+ checkVersion(json) {
244
+ const version = json.version || 1;
245
+ const supportedVersions = [1, 2];
246
+ if (!_.includes(supportedVersions, version)) {
247
+ throw new ConfigError(`Unsupported config version. Valid versions are: ${supportedVersions.join(', ')}`).atAttribute('version');
248
+ }
249
+
250
+ return version;
251
+ }
252
+
253
+ /**
254
+ * Checks 'engine-version' top-level attribute
255
+ *
256
+ * Optional. Under normal circumstances this will never be specified. It can be used to overlay an old
257
+ * config language over a new engine, if supported.
258
+ */
259
+ checkEngineVersion(json) {
260
+ const version = json.version || 1;
261
+ let engineVersion = json['engine-version'];
262
+ if (!engineVersion) {
263
+ if (version === 1) {
264
+ engineVersion = 1;
265
+ } else if (version === 2) {
266
+ engineVersion = 2;
267
+ }
268
+ }
269
+
270
+ const supportedEngineVersions = [1, 2];
271
+ if (!_.includes(supportedEngineVersions, engineVersion)) {
272
+ throw new ConfigError(`Unsupported engine version. Valid versions are: ${supportedEngineVersions.join(', ')}`).atAttribute('engine-version');
273
+ }
274
+ if (version === 2 && engineVersion === 1) {
275
+ throw new ConfigError('Config version 2 is not compatible with engine version 1').atAttribute('engine-version');
276
+ }
277
+
278
+ return engineVersion;
279
+ }
280
+
281
+ checkReplayCollection(json) {
282
+ let collectUntil = json['collect-replay-until'];
283
+ if (collectUntil) {
284
+ // Dates that include time component will be parsed in local time if no zone is specified; default to UTC
285
+ if (!collectUntil.search(/T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/)) {
286
+ collectUntil = `${collectUntil}Z`;
287
+ }
288
+
289
+ const untilTime = Date.parse(collectUntil);
290
+ if (Number.isNaN(untilTime)) {
291
+ throw new ConfigError(`Invalid ISO date: ${collectUntil}`).atAttribute('collect-replay-until');
292
+ }
293
+
294
+ return untilTime;
295
+ }
296
+
297
+ return null;
298
+ }
299
+
300
+ /**
301
+ * Checks 'device' top-level attribute
302
+ *
303
+ * Required. Devices are data sources, such as IO devices or tag servers.
304
+ */
305
+ checkDevice(json) {
306
+ const device = json.device;
307
+ if (!device) {
308
+ throw new ConfigError('No device specified');
309
+ }
310
+
311
+ const supportedDevices = [
312
+ 'labjack-u3',
313
+ 'labjack-t4',
314
+ 'labjack-t7',
315
+ 'opc-ua',
316
+ 'brother',
317
+ 'brother-http',
318
+ 'adam-6052',
319
+ 'mtconnect',
320
+ 'mtconnect-haas',
321
+ 'mtconnect-adapter',
322
+ 'modbus-tcp',
323
+ 'ethernet-ip',
324
+ 'mqtt',
325
+ 'mqtt-lincoln',
326
+ 'iolink-ifm-iot',
327
+ 'json-http',
328
+ 'local',
329
+ 'ab-pccc',
330
+ ];
331
+ if (!_.includes(supportedDevices, device)) {
332
+ throw new ConfigError(`Unsupported device. Valid devices are: ${supportedDevices.join(', ')}`).atAttribute('device');
333
+ }
334
+
335
+ return device;
336
+ }
337
+ }
338
+
339
+ module.exports = Config;