@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,535 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const math = require('../math');
5
+ const ConfigError = require('./configError');
6
+ const AllowDenyList = require('./common/allowDenyList');
7
+
8
+ const allowDenyList = new AllowDenyList();
9
+
10
+ class AdapterConfig {
11
+ constructor(expressionService, engineConfig) {
12
+ this.expressionService = expressionService;
13
+ this.engineConfig = engineConfig;
14
+
15
+ _.each(this.getExpressions(expressionService.config), name => expressionService.addExpression(name));
16
+ _.each(this.getStringExpressions(expressionService.config), name => expressionService.addStringExpression(name));
17
+ }
18
+
19
+ getExpressionsForDataItem(key, value) {
20
+ if (!value) {
21
+ return [];
22
+ }
23
+
24
+ if (_.isString(value)) {
25
+ return [{
26
+ path: `data-items.${key}.value`,
27
+ expression: value,
28
+ }];
29
+ }
30
+
31
+ if (_.isObject(value)) {
32
+ return _(value).map((expression, state) => {
33
+ if (_.isString(expression)) {
34
+ return {
35
+ path: `data-items.${key}.value.${state}`,
36
+ expression,
37
+ };
38
+ }
39
+ return null;
40
+ }).compact().value();
41
+ }
42
+
43
+ return [];
44
+ }
45
+
46
+ getExpressions(config = {}) {
47
+ let dataItems = config['data-items'];
48
+ if (_.isArray(dataItems)) {
49
+ dataItems = this.convertDataItemsList(dataItems);
50
+ }
51
+
52
+ dataItems = _(dataItems).map((container, key) => {
53
+ return this.getExpressionsForDataItem(key, container.value);
54
+ }).flattenDeep().compact().value();
55
+
56
+ const conditions = _(config.conditions).map((container, key) => {
57
+ if (_.isObject(container) && container.source) {
58
+ if (_.isObject(container.source)) {
59
+ const exprs = [];
60
+ if (container.source['code-list']) {
61
+ exprs.push({
62
+ path: `conditions.${key}.source.codeList`,
63
+ expression: container.source['code-list'],
64
+ });
65
+ }
66
+ if (container.source['message-list']) {
67
+ exprs.push({
68
+ path: `conditions.${key}.source.messageList`,
69
+ expression: container.source['message-list'],
70
+ });
71
+ }
72
+ if (container.source['level-list']) {
73
+ exprs.push({
74
+ path: `conditions.${key}.source.levelList`,
75
+ expression: container.source['level-list'],
76
+ });
77
+ }
78
+
79
+ return exprs;
80
+ }
81
+ }
82
+
83
+ if (!_.isArray(container)) {
84
+ container = [container];
85
+ }
86
+
87
+ return _.map(container, (subContainer) => {
88
+ if (_.isObject(subContainer)) {
89
+ return _.map(subContainer.value, (expression, state) => {
90
+ if (_.isString(expression)) {
91
+ return {
92
+ path: `conditions.${key}.${subContainer.code || key}.value.${state}`,
93
+ expression,
94
+ };
95
+ }
96
+ return null;
97
+ });
98
+ }
99
+ return null;
100
+ });
101
+ }).flattenDeep().compact().value();
102
+
103
+ return [...dataItems, ...conditions];
104
+ }
105
+
106
+ getStringExpressions(config = {}) {
107
+ return _(config.conditions).map((container, key) => {
108
+ if (!_.isArray(container)) {
109
+ container = [container];
110
+ }
111
+
112
+ return _.map(container, (subContainer) => {
113
+ if (_.isObject(subContainer)) {
114
+ const result = [];
115
+ if (_.isString(subContainer.code)) {
116
+ result.push({
117
+ path: `conditions.${key}.${subContainer.code || key}.code`,
118
+ string: subContainer.code,
119
+ });
120
+ }
121
+ if (_.isString(subContainer.message)) {
122
+ result.push({
123
+ path: `conditions.${key}.${subContainer.code || key}.message`,
124
+ string: subContainer.message,
125
+ });
126
+ }
127
+ _.each(subContainer.overrides, (override, index) => {
128
+ if (_.isString(override['override-code'])) {
129
+ result.push({
130
+ path: `conditions.${key}.overrides.${index}.override-code`,
131
+ string: override['override-code'],
132
+ withThis: true,
133
+ });
134
+ }
135
+ if (_.isString(override['override-message'])) {
136
+ result.push({
137
+ path: `conditions.${key}.overrides.${index}.override-message`,
138
+ string: override['override-message'],
139
+ withThis: true,
140
+ });
141
+ }
142
+ });
143
+ return result;
144
+ }
145
+ return null;
146
+ });
147
+ }).flattenDeep().compact().value();
148
+ }
149
+
150
+ parse(config = {}) {
151
+ this.loadDataItems(config['data-items']);
152
+ this.loadConditions(config.conditions);
153
+
154
+ this.passthroughConditions = _(this.conditions).filter((cond) => {
155
+ return cond.passthroughSource;
156
+ }).groupBy('passthroughSource').value();
157
+
158
+ this.verboseShdr = config['verbose-shdr'];
159
+ }
160
+
161
+ convertDataItemsList(items) {
162
+ const reformed = {};
163
+
164
+ const checkDup = (name) => {
165
+ if (reformed[name]) {
166
+ // Could be relaxed to a warning in the future
167
+ const configErr = new ConfigError(`Repeated symbol '${name}' in data-items list`);
168
+ throw configErr.atSection('data-items');
169
+ }
170
+ };
171
+
172
+ _.each(items, (entry) => {
173
+ if (_.isString(entry)) {
174
+ checkDup(entry);
175
+ if (!this.expressionService.isNameDefined(entry)) {
176
+ const configErr = new ConfigError(`Unknown symbol '${entry}' in data-items list`);
177
+ throw configErr.atSection('data-items');
178
+ }
179
+
180
+ reformed[entry] = { value: entry };
181
+ } else if (_.isObject(entry)) {
182
+ if (_.size(entry) !== 1) {
183
+ const configErr = new ConfigError('Unexpected object in data-items list');
184
+ throw configErr.atSection('data-items');
185
+ }
186
+
187
+ const name = Object.keys(entry)[0];
188
+ checkDup(name);
189
+
190
+ reformed[name] = { value: entry[name] };
191
+ }
192
+ });
193
+
194
+ return reformed;
195
+ }
196
+
197
+ loadDataItems(items) {
198
+ this.dataItemsByExpression = {};
199
+
200
+ // Abbreviated list format...
201
+ // data-items:
202
+ // - exec1
203
+ // - part_count
204
+ // - etc
205
+
206
+ // ...and shorthand expression list format...
207
+ // data-items:
208
+ // - exec1: some.value
209
+ // - part_count: part + expres
210
+
211
+ if (_.isArray(items)) {
212
+ items = this.convertDataItemsList(items);
213
+ }
214
+
215
+ // ...maps to legacy dictionary format
216
+ // data-items:
217
+ // exec1:
218
+ // value: exec1
219
+ // part_count:
220
+ // value: part_count
221
+ // etc:
222
+ // value: etc # or more complex expressions
223
+ this.dataItems = {};
224
+
225
+ _.each(items, (item, key) => {
226
+ this.loadDataItemInternal(key, item.value);
227
+ });
228
+ }
229
+
230
+ loadDataItem(key, value) {
231
+ this.loadDataItemInternal(key, value);
232
+
233
+ const exprs = this.getExpressionsForDataItem(key, value);
234
+ _.each(exprs, name => this.expressionService.addExpression(name));
235
+ }
236
+
237
+ loadDataItemInternal(key, value) {
238
+ const itemDefn = {
239
+ name: key,
240
+ };
241
+
242
+ if (_.isEmpty(value)) {
243
+ const configErr = new ConfigError(`Data item '${key}' has no value`);
244
+ throw configErr.atSection(`data-items.${key}`).atAttribute('value');
245
+ } else if (_.isString(value)) {
246
+ try {
247
+ const expr = value.toString();
248
+ const compiledExpr = this.expressionService.compileExpression(expr);
249
+ const exprPath = `data-items.${key}.value`;
250
+
251
+ this.dataItemsByExpression[exprPath] = key;
252
+
253
+ itemDefn.rule = {
254
+ rawExpression: expr,
255
+ expression: compiledExpr,
256
+ triggers: this.expressionService.expressionTriggers(exprPath),
257
+ };
258
+ } catch (err) {
259
+ const configErr = new ConfigError(`Problem evaluating expression: ${err.message}`);
260
+ throw configErr.atSection(`data-items.${key}`).atAttribute('value');
261
+ }
262
+ } else if (_.isObject(value)) {
263
+ const keys = Object.getOwnPropertyNames(value);
264
+ itemDefn.stateRules = _.map(keys, (name) => {
265
+ try {
266
+ const expr = value[name].toString();
267
+ const compiledExpr = this.expressionService.compileExpression(expr);
268
+ const exprPath = `data-items.${key}.value.${name}`;
269
+
270
+ this.dataItemsByExpression[exprPath] = key;
271
+
272
+ return {
273
+ value: name,
274
+ expression: compiledExpr,
275
+ triggers: this.expressionService.expressionTriggers(exprPath),
276
+ };
277
+ } catch (err) {
278
+ const configErr = new ConfigError(`Problem evaluating expression: ${err.message}`);
279
+ throw configErr.atSection(`data-items.${key}`).atAttribute(name);
280
+ }
281
+ });
282
+ } else {
283
+ const configErr = new ConfigError(`Data item '${key}' has unexpected value: ${value}`);
284
+ throw configErr.atSection(`data-items.${key}`).atAttribute('value');
285
+ }
286
+
287
+ this.dataItems[key] = itemDefn;
288
+ }
289
+
290
+ loadConditions(conditions) {
291
+ this.conditionsByExpression = {};
292
+ this.conditions = _.mapValues(conditions || {}, (item, key) => {
293
+ if (item.source) {
294
+ let source = {};
295
+ if (_.isString(item.source)) {
296
+ if (!this.expressionService.isNameDefined(item.source)) {
297
+ const configErr = new ConfigError(`Unknown symbol ${item.source} as condition source.`);
298
+ throw configErr.atSection(`conditions.${key}`).atAttribute('source');
299
+ }
300
+
301
+ source = {
302
+ name: key,
303
+ passthroughSource: item.source,
304
+ };
305
+ } else if (_.isObject(item.source)) {
306
+ if (item.source['code-list'] || item.source['message-list']) {
307
+ const codeList = item.source['code-list'];
308
+ if (!_.isString(codeList) || !this.expressionService.isNameDefined(codeList)) {
309
+ const configErr = new ConfigError(`Unknown symbol ${codeList} as condition source code list.`);
310
+ throw configErr.atSection(`conditions.${key}.source`).atAttribute('code-list');
311
+ }
312
+
313
+ const messageList = item.source['message-list'];
314
+ if (!_.isString(messageList) || !this.expressionService.isNameDefined(messageList)) {
315
+ const configErr = new ConfigError(`Unknown symbol ${messageList} as condition source message list.`);
316
+ throw configErr.atSection(`conditions.${key}.source`).atAttribute('message-list');
317
+ }
318
+
319
+ // Levels are optional
320
+ const levelList = item.source['level-list'];
321
+ if (levelList && (!_.isString(levelList) || !this.expressionService.isNameDefined(levelList))) {
322
+ const configErr = new ConfigError(`Unknown symbol ${levelList} as condition source level list.`);
323
+ throw configErr.atSection(`conditions.${key}.source`).atAttribute('level-list');
324
+ }
325
+
326
+ source = {
327
+ name: key,
328
+ arraySource: {
329
+ codeList,
330
+ messageList,
331
+ levelList,
332
+ },
333
+ };
334
+
335
+ this.conditionsByExpression[`conditions.${key}.source.codeList`] = key;
336
+ this.conditionsByExpression[`conditions.${key}.source.messageList`] = key;
337
+ this.conditionsByExpression[`conditions.${key}.source.levelList`] = key;
338
+ }
339
+ }
340
+
341
+ if (item['allow-codes']) {
342
+ source.allowCodes = allowDenyList.parseWildcardKeyList(item['allow-codes'], `conditions.${key}.allow-codes`);
343
+ }
344
+ if (item['deny-codes']) {
345
+ source.denyCodes = allowDenyList.parseWildcardKeyList(item['deny-codes'], `conditions.${key}.deny-codes`);
346
+ }
347
+ if (item['reclassify-warning']) {
348
+ source.reclassifyWarning = allowDenyList.parseWildcardKeyList(item['reclassify-warning'], `conditions.${key}.reclassify-warning-codes`);
349
+ }
350
+ if (item['reclassify-fault']) {
351
+ source.reclassifyFault = allowDenyList.parseWildcardKeyList(item['reclassify-fault'], `conditions.${key}.reclassify-fault-codes`);
352
+ }
353
+
354
+ if (item.overrides) {
355
+ if (!_.isArray(item.overrides)) {
356
+ const configErr = new ConfigError('Overrides must be a list');
357
+ throw configErr.atSection(`conditions.${key}`).atAttribute('overrides');
358
+ }
359
+
360
+ source.overrides = _.map(item.overrides, (override, index) => {
361
+ const defn = {};
362
+ if (override['match-code']) {
363
+ defn.matchCode = allowDenyList.parseWildcardKey(override['match-code'], `conditions.${key}.overrides.match-code`, 1);
364
+ }
365
+ if (override['match-message']) {
366
+ defn.matchMessage = allowDenyList.parseWildcardKey(override['match-message'], `conditions.${key}.overrides.match-message`, 1);
367
+ }
368
+
369
+ if (override['override-code']) {
370
+ defn.code = override['override-code'];
371
+ defn.codePath = `conditions.${key}.overrides.${index}.override-code`;
372
+ defn.codeIsStatic = this.expressionService.isStaticExpression(defn.codePath);
373
+ defn.codeResolver = this.expressionResolver(defn.code, defn.codePath);
374
+ defn.codeResolverTriggers = this.expressionService.expressionTriggers(defn.codePath);
375
+ this.conditionsByExpression[defn.codePath] = key;
376
+ }
377
+
378
+ if (override['override-message']) {
379
+ defn.message = override['override-message'];
380
+ defn.messagePath = `conditions.${key}.overrides.${index}.override-message`;
381
+ defn.messageIsStatic = this.expressionService.isStaticExpression(defn.messagePath);
382
+ defn.messageResolver = this.expressionResolver(defn.code, defn.messagePath);
383
+ defn.messageResolverTriggers = this.expressionService.expressionTriggers(defn.messagePath);
384
+ this.conditionsByExpression[defn.messagePath] = key;
385
+ }
386
+
387
+ return defn;
388
+ });
389
+ }
390
+
391
+ if (!source.name) {
392
+ const configErr = new ConfigError('Incomplete condition source definition.');
393
+ throw configErr.atSection(`conditions.${key}`).atAttribute('source');
394
+ }
395
+
396
+ return source;
397
+ }
398
+
399
+ if (!_.isArray(item)) {
400
+ item = [item];
401
+ }
402
+
403
+ const codesByTrigger = {};
404
+ const codes = _.map(item, (item) => {
405
+ const code = this.parseConditionCodes(item, key);
406
+
407
+ const ruleTriggers = _.flatten(_.map(code.stateRules, 'triggers'));
408
+ const triggers = _.union(ruleTriggers, code.codeResolverTriggers, code.messageResolverTriggers);
409
+
410
+ _.each(triggers, (trigger) => {
411
+ const codeList = codesByTrigger[trigger] ?? [];
412
+ codeList.push(code);
413
+ codesByTrigger[trigger] = codeList;
414
+ });
415
+
416
+ return code;
417
+ });
418
+
419
+ return {
420
+ name: key,
421
+ codes,
422
+ codesByTrigger,
423
+ };
424
+ });
425
+ }
426
+
427
+ expressionResolver(item, itemPath) {
428
+ return (engine, thisItem) => {
429
+ if (this.expressionService.hasStringExpression(itemPath)) {
430
+ try {
431
+ if (engine.getState) {
432
+ const sources = this.expressionService.expressionTriggers(itemPath);
433
+ const localState = {};
434
+ const unavailTerm = sources.reduce((res, name) => {
435
+ const val = engine.getState(name);
436
+ localState[name] = val?.value;
437
+ return res || !val || !val.available;
438
+ }, false);
439
+
440
+ if (unavailTerm) {
441
+ return null;
442
+ }
443
+
444
+ if (thisItem !== undefined) {
445
+ localState.this = thisItem;
446
+ }
447
+
448
+ return this.expressionService.computeStringExpression(itemPath, localState);
449
+ }
450
+ } catch (err) {
451
+ // If compute failed, return the raw template string
452
+ // TODO: Log or present failure through other means
453
+ return item;
454
+ }
455
+ }
456
+ return item;
457
+ };
458
+ }
459
+
460
+ parseConditionCodes(item, key) {
461
+ const conditionKeys = [];
462
+ if (item.value.FAULT) {
463
+ conditionKeys.push('FAULT');
464
+ }
465
+ if (item.value.WARNING) {
466
+ conditionKeys.push('WARNING');
467
+ }
468
+
469
+ const stateRules = _.map(conditionKeys, (name) => {
470
+ try {
471
+ const expr = item.value[name].toString();
472
+ const compiledExpr = this.expressionService.compileExpression(expr);
473
+ const exprPath = `conditions.${key}.${item.code || key}.value.${name}`;
474
+
475
+ this.conditionsByExpression[exprPath] = key;
476
+
477
+ return {
478
+ value: name,
479
+ expression: compiledExpr,
480
+ triggers: this.expressionService.expressionTriggers(exprPath),
481
+ };
482
+ } catch (err) {
483
+ const configErr = new ConfigError(`Problem evaluating expression: ${err.message}`);
484
+ throw configErr.atSection(`conditions.${key}.${name}`).atAttribute('value');
485
+ }
486
+ });
487
+
488
+ stateRules.push({
489
+ value: 'NORMAL',
490
+ expression: math.compile('true'),
491
+ triggers: [],
492
+ });
493
+
494
+ const code = item.code || key;
495
+ const codePath = `conditions.${key}.${code}.code`;
496
+ if (this.conditionsByExpression[codePath]) {
497
+ const configErr = new ConfigError(`Duplicate condition code '${code}' for key '${key}'`);
498
+ throw configErr.atSection(`conditions.${key}`);
499
+ }
500
+
501
+ const codeLookup = this.expressionResolver(code, codePath);
502
+ this.conditionsByExpression[codePath] = key;
503
+
504
+ const message = item.message || 'Condition triggered';
505
+ const messagePath = `conditions.${key}.${code}.message`;
506
+ const messageLookup = this.expressionResolver(message, messagePath);
507
+ this.conditionsByExpression[messagePath] = key;
508
+
509
+ return {
510
+ codePath,
511
+ messagePath,
512
+ code,
513
+ codeIsStatic: this.expressionService.isStaticExpression(codePath),
514
+ codeResolver: codeLookup,
515
+ codeResolverTriggers: this.expressionService.expressionTriggers(codePath),
516
+ message,
517
+ messageIsStatic: this.expressionService.isStaticExpression(messagePath),
518
+ messageResolver: messageLookup,
519
+ messageResolverTriggers: this.expressionService.expressionTriggers(messagePath),
520
+ stateRules,
521
+ };
522
+ }
523
+
524
+ triggeredDataItems(name) {
525
+ const triggeredPaths = this.expressionService.expressionsTriggeredBy(name);
526
+ return _(triggeredPaths).map(p => this.dataItemsByExpression[p]).compact().uniq().value();
527
+ }
528
+
529
+ triggeredConditions(name) {
530
+ const triggeredPaths = this.expressionService.expressionsTriggeredBy(name);
531
+ return _(triggeredPaths).map(p => this.conditionsByExpression[p]).compact().uniq().value();
532
+ }
533
+ }
534
+
535
+ module.exports = AdapterConfig;
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('../configError');
5
+
6
+ class AllowDenyList {
7
+ parseWildcardKeyList(keys, attributePath) {
8
+ if (_.isEmpty(keys)) {
9
+ return [];
10
+ }
11
+ if (!_.isArray(keys)) {
12
+ throw new ConfigError('list expected').atPath(attributePath);
13
+ }
14
+
15
+ // Check for duplicate entries
16
+ const duplicates = _(keys)
17
+ .groupBy()
18
+ .pickBy(x => x.length > 1)
19
+ .keys()
20
+ .value();
21
+
22
+ if (duplicates.length > 0) {
23
+ throw new ConfigError(`duplicate entries found: ${duplicates.join(', ')}`).atPath(attributePath);
24
+ }
25
+
26
+ let line = 0;
27
+ return _.map(keys, (key) => {
28
+ line += 1;
29
+ return this.parseWildcardKey(key, attributePath, line);
30
+ });
31
+ }
32
+
33
+ parseWildcardKey(key, attributePath, line) {
34
+ if (!_.isString(key)) {
35
+ throw new ConfigError(this.formatErrorMessage('Unexpected key pattern', line)).atPath(attributePath);
36
+ }
37
+ if (_.first(key) === '/') {
38
+ const match = key.match(/^\/(.+)\/([imus]*)$/sm);
39
+ if (match) {
40
+ const flags = match[2];
41
+ return new RegExp(key.replace(/^\/\^?/, '^').replace(/\$?\/[imus]*$/, '$'), flags);
42
+ }
43
+ }
44
+
45
+ /* eslint-disable no-useless-escape, prefer-template */
46
+ const escapeRegex = str => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
47
+ return new RegExp('^' + key.split('*').map(escapeRegex).join('.*') + '$');
48
+ }
49
+
50
+ formatErrorMessage(message, line) {
51
+ if (_.isUndefined(line)) {
52
+ return message;
53
+ }
54
+ return `${message} at entry ${line} of list`;
55
+ }
56
+ }
57
+
58
+ module.exports = AllowDenyList;
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const ConfigError = require('../configError');
4
+
5
+ // https://machinemetrics.atlassian.net/wiki/spaces/~279526106/pages/492404812/Documentation+Extended+JSON+Path+Syntax
6
+ const checkJsonPropPath = (path, name) => {
7
+ if (!path) {
8
+ throw new ConfigError('Malformed prop definition. Missing path.').atAttribute(name);
9
+ }
10
+
11
+ const pathSegments = path.split('.');
12
+
13
+ // Validate each segment in the path
14
+ pathSegments.forEach((segment) => {
15
+ const findMatch = segment.match(/\[([a-zA-Z0-9_]+)=([a-zA-Z0-9_]+)]/);
16
+
17
+ // If find notation is detected
18
+ if (findMatch) {
19
+ const [, key, rawValue] = findMatch;
20
+ const baseSegment = segment.replace(findMatch[0], '');
21
+
22
+ // Check that the base segment (if present) is a non-empty string
23
+ if (baseSegment && baseSegment.length === 0) {
24
+ throw new ConfigError('Malformed prop definition. Invalid path segment.').atAttribute(name);
25
+ }
26
+
27
+ // Validate key (should be a non-empty string)
28
+ if (key.length === 0) {
29
+ throw new ConfigError('Malformed prop definition. Invalid key in segment.').atAttribute(name);
30
+ }
31
+
32
+ // Validate value (should be a string or a number)
33
+ if (!/^[\d]+([.][\d]+)?$/g.test(rawValue) && rawValue.length === 0) {
34
+ throw new ConfigError('Malformed prop definition. Invalid value in segment.').atAttribute(name);
35
+ }
36
+ } else if (segment.length === 0) {
37
+ throw new ConfigError('Malformed prop definition. Invalid path segment.').atAttribute(name);
38
+ }
39
+ });
40
+ };
41
+
42
+ module.exports = {
43
+ checkJsonPropPath,
44
+ };