@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,215 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('../configError');
5
+
6
+ class BrotherHTTPConfig {
7
+ constructor(expressionService) {
8
+ this.expressionService = expressionService;
9
+ this.macroSettings = expressionService.config['macro-settings'] || {};
10
+
11
+ this.DataCollectionModes =
12
+ {
13
+ HTTP: 'HTTP',
14
+ FTP: 'FTP',
15
+ MIXED: 'MIXED',
16
+ };
17
+
18
+ const mode = expressionService.config['collection-mode'];
19
+ const defaultMode = this.DataCollectionModes.MIXED;
20
+ if (mode === undefined) this.dataCollectionMode = defaultMode;
21
+ else if (mode.toLowerCase() === 'http') this.dataCollectionMode = this.DataCollectionModes.HTTP;
22
+ else if (mode.toLowerCase() === 'ftp') this.dataCollectionMode = this.DataCollectionModes.FTP;
23
+ else if (mode.toLowerCase() === 'mixed') this.dataCollectionMode = this.DataCollectionModes.MIXED;
24
+
25
+ if (this.dataCollectionMode === this.DataCollectionModes.FTP) {
26
+ this.macros = expressionService.config.macros || {};
27
+ } else {
28
+ this.macros = {};
29
+ }
30
+
31
+ _.each(
32
+ this.getIdentifiers(_.keys(this.macros)),
33
+ name => this.expressionService.addName(name)
34
+ );
35
+ }
36
+
37
+ getFixedKeys() {
38
+ if (this.dataCollectionMode === this.DataCollectionModes.HTTP) {
39
+ return [
40
+ 'part_count',
41
+ 'custom_part_count_1',
42
+ 'custom_part_count_2',
43
+ 'custom_part_count_3',
44
+ 'custom_part_count_4',
45
+ 'operating_status',
46
+ 'program_number_1',
47
+ 'program_number_2',
48
+ 'execution',
49
+ 'machine_name',
50
+ 'machine_model',
51
+ ];
52
+ } else if (this.dataCollectionMode === this.DataCollectionModes.MIXED) {
53
+ return [
54
+ 'part_count',
55
+ 'custom_part_count_1',
56
+ 'custom_part_count_2',
57
+ 'custom_part_count_3',
58
+ 'custom_part_count_4',
59
+ 'operating_status',
60
+ 'program_number_1',
61
+ 'program_number_2',
62
+ 'execution',
63
+ 'program_comment_1',
64
+ 'program_comment_2',
65
+ 'machine_name',
66
+ 'machine_model',
67
+ ];
68
+ } else if (this.dataCollectionMode === this.DataCollectionModes.FTP) {
69
+ return [
70
+ 'part_count',
71
+ 'custom_part_count_1',
72
+ 'custom_part_count_2',
73
+ 'custom_part_count_3',
74
+ 'custom_part_count_4',
75
+ 'operating_status',
76
+ 'cutting_1',
77
+ 'cutting_2',
78
+ 'program_number_1',
79
+ 'program_number_2',
80
+ 'rapid_traverse_override',
81
+ 'feedrate_override',
82
+ 'spindle_override',
83
+ 'feedrate',
84
+ 'spindle_speed',
85
+ 'execution',
86
+ 'emergency_stop',
87
+ 'program_comment_1',
88
+ 'program_comment_2',
89
+ 'controller_mode',
90
+ ];
91
+ } else return [];
92
+ }
93
+
94
+ getIdentifiers(macroKeys) {
95
+ return _.map(this.getFixedKeys().concat(macroKeys), (name) => {
96
+ return {
97
+ name,
98
+ channel: 'device',
99
+ defaultValue: 0,
100
+ };
101
+ });
102
+ }
103
+
104
+ /** For non-FTP modes a determination of whether or not part_count
105
+ * is available can only be made in the adapter; exclude here, added
106
+ * in adapter as appropriate. Do the same for program_number_2 (mixed/http) and
107
+ * program_comment_2 (mixed), meaningful only for two pallet machines.
108
+ *
109
+ * Leave the FTP mode alone since it is at this point at any rate considered experimental.
110
+ */
111
+ postParse(config) {
112
+ if (this.dataCollectionMode === this.DataCollectionModes.HTTP ||
113
+ this.dataCollectionMode === this.DataCollectionModes.MIXED) {
114
+ _.each(this.getFixedKeys().concat(_.map(this.macros, (_value, key) => { return key; })), (key) => {
115
+ if (key !== 'part_count' && key !== 'program_number_2' && key !== 'program_comment_2') {
116
+ config.adapter.loadDataItem(key, key);
117
+ }
118
+ });
119
+ } else {
120
+ _.each(this.getFixedKeys().concat(_.map(this.macros, (_value, key) => { return key; })), (key) => {
121
+ config.adapter.loadDataItem(key, key);
122
+ });
123
+ }
124
+ }
125
+
126
+ parse(config = {}) {
127
+ this.endpoint = config.endpoint;
128
+ this.username = config.username;
129
+ this.password = config.password;
130
+
131
+ if (!config.endpoint) {
132
+ throw new ConfigError('No endpoint defined').atAttribute('endpoint');
133
+ }
134
+
135
+ const [host, port] = config.endpoint.split(':');
136
+ this.host = host;
137
+ this.port = port || 21;
138
+
139
+ this.scanInterval = this.checkScanInterval(config);
140
+ this.httpInterval = this.checkHttpInterval(config);
141
+
142
+ /** Configuration indicates how to collect data:
143
+ * HTTP only,
144
+ * FTP only (default),
145
+ * HTTP + FTP for program headers
146
+ */
147
+ let mode = config['collection-mode'] ?? '';
148
+ if (_.isString(mode)) {
149
+ mode = mode.toLowerCase();
150
+ }
151
+ if (!_.includes(['http', 'ftp', 'mixed'], mode)) {
152
+ throw new ConfigError('collection-mode must be one of: {http, ftp, mixed}').atAttribute('macros');
153
+ }
154
+
155
+ let keysThatAlreadyExist = '';
156
+ _.forEach(this.macros, (_value, key) => {
157
+ if (_.findIndex(this.getFixedKeys(), (itm) => { return itm === key; }) !== -1) {
158
+ keysThatAlreadyExist = `${keysThatAlreadyExist}${key}; `;
159
+ }
160
+ });
161
+ if (keysThatAlreadyExist !== '') {
162
+ throw new ConfigError(`The following macro keys are already in use: ${keysThatAlreadyExist}`).atAttribute('macros');
163
+ }
164
+
165
+ /** Macro program call control type set in the Data Bank -> G/M code macro section:
166
+ * 0 = Type 1
167
+ * 1 = Type 2
168
+ */
169
+ if (this.macroSettings.type === undefined) this.macroSettings.type = 'type1';
170
+ if (!(this.macroSettings.type === 'type1' || this.macroSettings.type === 'type2')) {
171
+ throw new ConfigError('The value of macro-settings key type must be either type1 or type2').atAttribute('type');
172
+ }
173
+
174
+ if (this.macroSettings.units === undefined) {
175
+ this.macroSettings.units = 'inches';
176
+ }
177
+ if (!(this.macroSettings.units === 'inches' || this.macroSettings.units === 'metric')) {
178
+ throw new ConfigError('The value of macro-settings key units must be either inches or metric').atAttribute('units');
179
+ }
180
+
181
+ if (this.macroSettings.databank === undefined) {
182
+ this.macroSettings.databank = 1;
183
+ }
184
+ const dataBankNum = Number(this.macroSettings.databank);
185
+ if (Number.isNaN(dataBankNum) || dataBankNum < 0 || dataBankNum > 10) {
186
+ throw new ConfigError('The value of macro-settings key databank must be 0 <= x <= 9').atAttribute('databank');
187
+ }
188
+
189
+ _.forEach(this.macros, (value, key) => {
190
+ if (value === '' || value === null || Number.isNaN(Number(value))) {
191
+ throw new ConfigError(`The value of macros key ${key} must be a valid integer`).atAttribute(key);
192
+ }
193
+ });
194
+ }
195
+
196
+ checkScanInterval(config) {
197
+ const scanIntvl = +(config['scan-interval'] || 1.0);
198
+ if (scanIntvl <= 0) {
199
+ throw new ConfigError('scan-interval must be > 0').atAttribute('scan-interval');
200
+ }
201
+
202
+ return Math.ceil(scanIntvl * 1000);
203
+ }
204
+
205
+ checkHttpInterval(config) {
206
+ const httpIntvl = +(config['http-interval'] || 0.05);
207
+ if (httpIntvl <= 0) {
208
+ throw new ConfigError('http-interval must be > 0').atAttribute('http-interval');
209
+ }
210
+
211
+ return Math.ceil(httpIntvl * 1000);
212
+ }
213
+ }
214
+
215
+ module.exports = BrotherHTTPConfig;
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('../configError');
5
+ const validations = require('../common/validations');
6
+
7
+ class EthernetIPConfig {
8
+ constructor(expressionService) {
9
+ this.expressionService = expressionService;
10
+
11
+ _.each(this.getIdentifiers(expressionService.config), name => this.expressionService.addName(name));
12
+ }
13
+
14
+ getIdentifiers(config = {}) {
15
+ return _.map(_.keys(config.tags), (name) => {
16
+ return {
17
+ name,
18
+ channel: 'device',
19
+ defaultValue: 0,
20
+ };
21
+ });
22
+ }
23
+
24
+ parse(config = {}) {
25
+ this.host = this.checkHost(config);
26
+ this.slot = this.checkSlot(config);
27
+ this.scanRate = this.checkScanInterval(config);
28
+ this.groupRead = this.checkGroupRead(config);
29
+ this.reset = validations.checkReset(config);
30
+ this.tags = this.loadTags(config.tags);
31
+ }
32
+
33
+ loadTags(tags) {
34
+ // Shorthand syntax: tags: { name: 'path' }
35
+ tags = _.mapValues(tags, (tag, name) => {
36
+ if (_.isString(tag)) {
37
+ // TODO: We'd really like to examine newlines but YAML strips them. Another yaml library could surface this.
38
+ if (/^path\s+/.test(tag)) {
39
+ throw new ConfigError('Malformed tag definition. Possible mix of shorthand and standard form.').atSection('tags').atAttribute(name);
40
+ }
41
+
42
+ return { path: tag };
43
+ }
44
+ return tag;
45
+ });
46
+
47
+ return _(tags).map((defn, name) => {
48
+ try {
49
+ const tagPath = this.checkTagPath(defn);
50
+
51
+ return {
52
+ name,
53
+ path: tagPath,
54
+ verboseSample: !!defn['verbose-sample'],
55
+ verboseEmit: !!defn['verbose-emit'],
56
+ };
57
+ } catch (error) {
58
+ if (error instanceof ConfigError) {
59
+ error.atSection(`tags.${name}`);
60
+ }
61
+ throw error;
62
+ }
63
+ }).keyBy('name').value();
64
+ }
65
+
66
+ checkHost(defn) {
67
+ const host = defn.host;
68
+ if (_.isEmpty(host)) {
69
+ throw new ConfigError('An IP or hostname must be specified').atPath('host');
70
+ }
71
+
72
+ return host;
73
+ }
74
+
75
+ checkSlot(defn) {
76
+ const slot = defn.slot || 0;
77
+ return slot;
78
+ }
79
+
80
+ checkScanInterval(defn) {
81
+ const scanRate = defn['scan-interval'] || 0.2;
82
+ if (scanRate < 0) {
83
+ throw new ConfigError('Scan interval must be > 0').atPath('scan-interval');
84
+ }
85
+
86
+ return scanRate * 1000;
87
+ }
88
+
89
+ checkGroupRead(defn) {
90
+ const groupRead = _.get(defn, 'group-read', true);
91
+ try {
92
+ return EthernetIPConfig.booleanCast(groupRead);
93
+ } catch (err) {
94
+ throw new ConfigError('Value must be true or false').atPath('group-read');
95
+ }
96
+ }
97
+
98
+ checkTagPath(defn) {
99
+ const path = defn.path;
100
+ if (!path) {
101
+ throw new ConfigError('No tag path specified');
102
+ }
103
+
104
+ if (!EthernetIPConfig.isValidTagname(path)) {
105
+ throw new ConfigError('Invalid tag path').atAttribute('path');
106
+ }
107
+
108
+ return path;
109
+ }
110
+
111
+ static booleanCast(value) {
112
+ if (_.isString(value)) {
113
+ value = value.toLowerCase();
114
+ } else if (_.isNumber(value)) {
115
+ value = value.toString();
116
+ }
117
+
118
+ if (value === true || value === 'true' || value === 'yes' || value === '1') {
119
+ return true;
120
+ }
121
+ if (value === false || value === 'false' || value === 'no' || value === '0') {
122
+ return false;
123
+ }
124
+
125
+ throw new Error();
126
+ }
127
+
128
+ /* eslint prefer-template: 0 */
129
+ // Tag validation borrowed from ethernet-ip project
130
+
131
+ static isValidTagname(tagname) {
132
+ // regex components
133
+ const nameRegex = (captureIndex) => {
134
+ return `(_?[a-zA-Z]|_\\d)(?:(?=(_?[a-zA-Z0-9]))\\${captureIndex})*`;
135
+ };
136
+
137
+ const multDimArrayRegex = '(\\[\\d+(,\\d+){0,2}])';
138
+ const arrayRegex = '(\\[\\d+])';
139
+ const bitIndexRegex = '(\\.\\d{1,2})';
140
+
141
+ // user regex for user tags
142
+ const userRegex = new RegExp(
143
+ '^(Program:' +
144
+ nameRegex(3) +
145
+ '\\.)?' + // optional program name
146
+ nameRegex(5) +
147
+ multDimArrayRegex +
148
+ '?' + // tag name
149
+ '(\\.' +
150
+ nameRegex(10) +
151
+ arrayRegex +
152
+ '?)*' + // option member name
153
+ bitIndexRegex +
154
+ '?$'
155
+ ); // optional bit index
156
+ // full user regex
157
+ // ^(Program:(_?[a-zA-Z]|_\d)(?:(?=(_?[a-zA-Z0-9]))\3)*\.)?(_?[a-zA-Z]|_\d)(?:(?=(_?[a-zA-Z0-9]))\5)*
158
+ // (\[\d+(,\d+){0,2}])?(\.(_?[a-zA-Z]|_\d)(?:(?=(_?[a-zA-Z0-9]))\10)*(\[\d+])?)*(\.\d{1,2})?$
159
+
160
+ // module regex for module tags
161
+ const moduleRegex = new RegExp(
162
+ '^' +
163
+ nameRegex(2) + // module name
164
+ '(:\\d{1,2})?' + // optional slot num (not required for rack optimized connections)
165
+ ':[IOC]' + // input/output/config
166
+ '(\\.' +
167
+ nameRegex(6) +
168
+ arrayRegex +
169
+ '?)?' + // optional member with optional array index
170
+ bitIndexRegex +
171
+ '?$'
172
+ ); // optional bit index
173
+ // full module regex
174
+ // ^(_?[a-zA-Z]|_\d)(?:(?=(_?[a-zA-Z0-9]))\2)*(:\d{1,2})?:[IOC](\.(_?[a-zA-Z]|_\d)(?:(?=(_?[a-zA-Z0-9]))\6)*
175
+ // (\[\d+])?)?(\.\d{1,2})?$
176
+
177
+ if (!userRegex.test(tagname) && !moduleRegex.test(tagname)) {
178
+ return false;
179
+ }
180
+
181
+ // check segments
182
+ if (tagname.split(/[:.[\],]/).filter(segment => segment.length > 40).length > 0) {
183
+ return false; // check that all segments are <= 40 char
184
+ }
185
+
186
+ // passed all tests
187
+ return true;
188
+ }
189
+ }
190
+
191
+ module.exports = EthernetIPConfig;
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const ConfigError = require('../configError');
5
+
6
+ class IfmIotConfig {
7
+ constructor(expressionService) {
8
+ this.expressionService = expressionService;
9
+
10
+ _.each(this.getIdentifiers(expressionService.config), name => this.expressionService.addName(name));
11
+ }
12
+
13
+ parse(config = {}) {
14
+ if (config.device !== this.device()) {
15
+ return;
16
+ }
17
+
18
+ this.endpoint = this.parseEndpoint(config.endpoint);
19
+ this.iolink = this.parseIOLink(config.iolink);
20
+ this.findIODD = this.checkFindIODD(config);
21
+ this.scanInterval = this.checkScanInterval(config);
22
+ this.deviceScanInterval = this.checkDeviceScanInterval(config);
23
+ this.declaredKeys = this.getDeclaredKeys(config);
24
+ }
25
+
26
+ device() {
27
+ return 'iolink-ifm-iot';
28
+ }
29
+
30
+ getIdentifiers(config = {}) {
31
+ const keys = [...this.getRecordKeys(config), ...this.getDeclaredKeys(config)];
32
+ return _.map(keys, (name) => {
33
+ return {
34
+ name,
35
+ channel: 'device',
36
+ defaultValue: 0,
37
+ };
38
+ });
39
+ }
40
+
41
+ getRecordKeys(config = {}) {
42
+ if (!_.isObject(config.iolink)) {
43
+ return [];
44
+ }
45
+
46
+ const ports = _.map(config.iolink, (port, name) => {
47
+ if (!name.match(/^port\d+$/)) {
48
+ return [];
49
+ }
50
+
51
+ const pdata = port['process-data'];
52
+ if (!_.isObject(pdata)) {
53
+ return [];
54
+ }
55
+
56
+ return _.map(pdata, (record, rname) => {
57
+ return rname;
58
+ });
59
+ });
60
+
61
+ return _.flatten(ports);
62
+ }
63
+
64
+ getDeclaredKeys(config = {}) {
65
+ let attribute = 'declare-keys';
66
+ if (!config[attribute] && config.keys) {
67
+ attribute = 'keys';
68
+ }
69
+
70
+ const keys = config[attribute];
71
+ if (_.isEmpty(keys)) {
72
+ return [];
73
+ }
74
+ if (!_.isArray(keys)) {
75
+ throw new ConfigError('list expected').atAttribute(attribute);
76
+ }
77
+
78
+ return keys;
79
+ }
80
+
81
+ checkScanInterval(defn) {
82
+ const scanRate = defn['scan-interval'] || 1;
83
+ if (scanRate <= 0) {
84
+ throw new ConfigError('Scan interval must be > 0').atPath('scan-interval');
85
+ }
86
+
87
+ return scanRate * 1000;
88
+ }
89
+
90
+ checkDeviceScanInterval(defn) {
91
+ const scanRate = defn['device-scan-interval'] || 10;
92
+ if (scanRate < 0) {
93
+ throw new ConfigError('Device scan interval must be >= 0').atPath('device-scan-interval');
94
+ }
95
+
96
+ return scanRate * 1000;
97
+ }
98
+
99
+ parseEndpoint(endpoint) {
100
+ if (!endpoint) {
101
+ throw new ConfigError('No endpoint defined').atAttribute('endpoint');
102
+ }
103
+
104
+ // HTTP/S URLs are returned directly
105
+ if (endpoint.match(/^https?:\/\//)) {
106
+ return endpoint;
107
+ } else if (endpoint.match(/^\w+:\/\//)) {
108
+ throw new ConfigError('Unexpected endpoint protocol').atAttribute('endpoint');
109
+ }
110
+
111
+ // Assume HTTP protocol by default
112
+ return `http://${endpoint}`;
113
+ }
114
+
115
+ parseIOLink(iolink) {
116
+ const ports = [];
117
+ const keys = _.keys(iolink);
118
+
119
+ _.each(keys, (key) => {
120
+ const match = key.match(/^port(\d+)$/);
121
+ if (match === null) {
122
+ return;
123
+ }
124
+
125
+ const portNum = match[1];
126
+ ports.push(this.parsePort(iolink[key], portNum));
127
+ });
128
+
129
+ return ports;
130
+ }
131
+
132
+ parsePort(port, portNum) {
133
+ return {
134
+ portNumber: portNum,
135
+ enabled: this.parseBool(port.enabled ?? true),
136
+ processData: this.parseProcessData(port),
137
+ };
138
+ }
139
+
140
+ parseProcessData(defn) {
141
+ const data = defn['process-data'] ?? {};
142
+ if (!_.isObject(data)) {
143
+ throw new ConfigError('Invalid processd data').atAttribute('process-data');
144
+ }
145
+
146
+ return _.map(data, this.parseProcessDataRecord.bind(this));
147
+ }
148
+
149
+ parseProcessDataRecord(record, name) {
150
+ return {
151
+ name,
152
+ enabled: this.parseBool(record.enabled ?? true),
153
+ bitOffset: this.checkBitOffset(record),
154
+ bitLength: this.checkBitLength(record),
155
+ type: this.checkType(record),
156
+ gradient: this.checkGradient(record),
157
+ offset: this.checkOffset(record),
158
+ };
159
+ }
160
+
161
+ checkPortEnabled(defn) {
162
+ return this.parseBool(defn.enabled);
163
+ }
164
+
165
+ checkBitOffset(defn) {
166
+ const offset = defn['bit-offset'];
167
+ if (_.isUndefined(offset)) {
168
+ throw new ConfigError('bit-offset must be specified').atAttribute('bit-offset');
169
+ }
170
+ if (!_.isNumber(offset) || offset < 0) {
171
+ throw new ConfigError('bit-offset must be >= 0').atAttribute('bit-offset');
172
+ }
173
+
174
+ return offset;
175
+ }
176
+
177
+ checkBitLength(defn) {
178
+ const length = defn['bit-length'];
179
+ if (_.isUndefined(length)) {
180
+ throw new ConfigError('bit-length must be specified').atAttribute('bit-length');
181
+ }
182
+ if (!_.isNumber(length) || length < 0) {
183
+ throw new ConfigError('bit-length must be >= 0').atAttribute('bit-length');
184
+ }
185
+
186
+ return length;
187
+ }
188
+
189
+ checkType(defn) {
190
+ let type = defn.type;
191
+ if (_.isString(type)) {
192
+ type = type.toLowerCase();
193
+ }
194
+
195
+ if (!_.includes(['bool', 'uint', 'int', 'float', 'booleant', 'uintegert', 'integert', 'float32t'], type)) {
196
+ throw new ConfigError('type must be one of the supported data types: [bool, uint, int, float]').atAttribute('type');
197
+ }
198
+
199
+ return type;
200
+ }
201
+
202
+ checkGradient(defn) {
203
+ const gradient = defn.gradient;
204
+ if (_.isUndefined(gradient)) {
205
+ return 1;
206
+ }
207
+ if (!_.isNumber(gradient)) {
208
+ throw new ConfigError('gradient must be numeric').atAttribute('gradient');
209
+ }
210
+
211
+ return gradient;
212
+ }
213
+
214
+ checkOffset(defn) {
215
+ const offset = defn.offset;
216
+ if (_.isUndefined(offset)) {
217
+ return 0;
218
+ }
219
+ if (!_.isNumber(offset)) {
220
+ throw new ConfigError('offset must be numeric').atAttribute('offset');
221
+ }
222
+
223
+ return offset;
224
+ }
225
+
226
+ checkFindIODD(defn) {
227
+ const findIODD = defn['find-iodd'];
228
+ if (_.isUndefined(findIODD)) {
229
+ return true;
230
+ }
231
+
232
+ return this.parseBool(findIODD);
233
+ }
234
+
235
+ parseBool(value) {
236
+ if (_.isString(value)) {
237
+ value = value.toLowerCase();
238
+ return value === 'true';
239
+ }
240
+
241
+ return !!_.toInteger(value);
242
+ }
243
+ }
244
+
245
+ module.exports = IfmIotConfig;