@machinemetrics/io-adapter-lib 2.35.1 → 2.37.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.
- package/CHANGELOG.md +6 -0
- package/lib/config/branchPreprocessor.js +123 -0
- package/lib/config/device/brotherHTTPConfig.js +47 -6
- package/lib/config/engineConfigV2.js +5 -0
- package/lib/config/index.js +2 -0
- package/lib/expressionService.js +6 -1
- package/lib/transform/branch.js +274 -0
- package/lib/transform/index.js +2 -0
- package/package.json +1 -1
- package/test/config/brother-ftp.test.js +63 -0
- package/test/config/brother-http.test.js +2 -0
- package/test/configFiles/device/brother-ftp-bad-cnc.yml +3 -0
- package/test/configFiles/device/brother-ftp-bad-mode.yml +3 -0
- package/test/configFiles/device/brother-ftp-bad-path.yml +3 -0
- package/test/configFiles/device/brother-ftp-cnc-legacy.yml +3 -0
- package/test/configFiles/device/brother-ftp-minimal.yml +2 -0
- package/test/configFiles/device/brother-ftp-paths.yml +11 -0
- package/test/configFiles/transform/branch.yml +117 -0
- package/test/configFiles/transform/invalid-branch-else-only.yml +11 -0
- package/test/configFiles/transform/invalid-branch-elseif-no-condition.yml +14 -0
- package/test/configFiles/transform/invalid-branch-elseif-only.yml +12 -0
- package/test/configFiles/transform/invalid-branch-if-no-condition.yml +11 -0
- package/test/configFiles/transform/invalid-branch-unrelated-op.yml +18 -0
- package/test/transform/branch-invalid.test.js +57 -0
- package/test/transform/branch.test.js +357 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.37.0]
|
|
4
|
+
- Brother config changes in support of d00 (c00<->d00 may not be the exact cutoff)
|
|
5
|
+
|
|
6
|
+
## [2.36.0]
|
|
7
|
+
- Added if / else-if / else branch transform
|
|
8
|
+
|
|
3
9
|
## [2.35.1]
|
|
4
10
|
- Fixed value-increase and value-decrease to not emit true when coming back from UNAVAILABLE
|
|
5
11
|
- Behavior change: value-decrease does not emit true on start
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const ConfigError = require('./configError');
|
|
5
|
+
|
|
6
|
+
const BRANCH_KEYS = ['if', 'else-if', 'else'];
|
|
7
|
+
|
|
8
|
+
function isBranchKey(key) {
|
|
9
|
+
return BRANCH_KEYS.includes(key);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getBranchKey(item) {
|
|
13
|
+
if (!_.isObject(item) || _.isArray(item)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const keys = _.keys(item);
|
|
17
|
+
if (keys.length !== 1) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const key = keys[0];
|
|
21
|
+
return isBranchKey(key) ? key : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Preprocesses a variable's transform list to collapse consecutive if/else-if/else
|
|
26
|
+
* into a single branch transform. The user-facing YAML syntax allows:
|
|
27
|
+
*
|
|
28
|
+
* - if:
|
|
29
|
+
* - condition: this == 1
|
|
30
|
+
* - op1
|
|
31
|
+
* - else-if:
|
|
32
|
+
* - condition: this == 2
|
|
33
|
+
* - op1
|
|
34
|
+
* - else:
|
|
35
|
+
* - op1
|
|
36
|
+
*
|
|
37
|
+
* This gets rewritten to:
|
|
38
|
+
*
|
|
39
|
+
* - branch:
|
|
40
|
+
* - if: [...]
|
|
41
|
+
* - else-if: [...]
|
|
42
|
+
* - else: [...]
|
|
43
|
+
*/
|
|
44
|
+
function preprocessBranchTransforms(transformList, sectionPath) {
|
|
45
|
+
if (!_.isArray(transformList)) {
|
|
46
|
+
return transformList;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = [];
|
|
50
|
+
let i = 0;
|
|
51
|
+
|
|
52
|
+
while (i < transformList.length) {
|
|
53
|
+
const key = getBranchKey(transformList[i]);
|
|
54
|
+
|
|
55
|
+
if (key !== 'if') {
|
|
56
|
+
result.push(transformList[i]);
|
|
57
|
+
i += 1;
|
|
58
|
+
} else {
|
|
59
|
+
const branchComponents = [];
|
|
60
|
+
let seenElse = false;
|
|
61
|
+
|
|
62
|
+
while (i < transformList.length) {
|
|
63
|
+
const currentKey = getBranchKey(transformList[i]);
|
|
64
|
+
|
|
65
|
+
if (currentKey === null) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (currentKey === 'if') {
|
|
70
|
+
if (branchComponents.length > 0) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const item = transformList[i];
|
|
74
|
+
const body = item[currentKey];
|
|
75
|
+
const condition = body[0];
|
|
76
|
+
const chainTransforms = preprocessBranchTransforms(body.slice(1), `${sectionPath}.${i}.if`);
|
|
77
|
+
branchComponents.push({ [currentKey]: [condition, ...chainTransforms] });
|
|
78
|
+
i += 1;
|
|
79
|
+
} else if (currentKey === 'else-if') {
|
|
80
|
+
if (branchComponents.length === 0) {
|
|
81
|
+
throw new ConfigError('else-if must follow if or else-if')
|
|
82
|
+
.atPath(sectionPath);
|
|
83
|
+
}
|
|
84
|
+
if (seenElse) {
|
|
85
|
+
throw new ConfigError('else-if cannot follow else')
|
|
86
|
+
.atPath(sectionPath);
|
|
87
|
+
}
|
|
88
|
+
const item = transformList[i];
|
|
89
|
+
const body = item[currentKey];
|
|
90
|
+
const condition = body[0];
|
|
91
|
+
const chainTransforms = preprocessBranchTransforms(body.slice(1), `${sectionPath}.${i}.else-if`);
|
|
92
|
+
branchComponents.push({ [currentKey]: [condition, ...chainTransforms] });
|
|
93
|
+
i += 1;
|
|
94
|
+
} else if (currentKey === 'else') {
|
|
95
|
+
if (branchComponents.length === 0) {
|
|
96
|
+
throw new ConfigError('else must follow if or else-if')
|
|
97
|
+
.atPath(sectionPath);
|
|
98
|
+
}
|
|
99
|
+
if (seenElse) {
|
|
100
|
+
throw new ConfigError('only one else allowed per branch')
|
|
101
|
+
.atPath(sectionPath);
|
|
102
|
+
}
|
|
103
|
+
seenElse = true;
|
|
104
|
+
const item = transformList[i];
|
|
105
|
+
const body = item[currentKey];
|
|
106
|
+
const chainTransforms = preprocessBranchTransforms(body, `${sectionPath}.${i}.else`);
|
|
107
|
+
branchComponents.push({ [currentKey]: chainTransforms });
|
|
108
|
+
i += 1;
|
|
109
|
+
} else {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
result.push({ branch: branchComponents });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
preprocessBranchTransforms,
|
|
123
|
+
};
|
|
@@ -16,7 +16,10 @@ class BrotherHTTPConfig {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const mode = expressionService.config['collection-mode'];
|
|
19
|
-
const
|
|
19
|
+
const deviceName = expressionService.config.device;
|
|
20
|
+
const defaultMode = deviceName === 'brother-ftp'
|
|
21
|
+
? this.DataCollectionModes.FTP
|
|
22
|
+
: this.DataCollectionModes.MIXED;
|
|
20
23
|
if (mode === undefined) this.dataCollectionMode = defaultMode;
|
|
21
24
|
else if (mode.toLowerCase() === 'http') this.dataCollectionMode = this.DataCollectionModes.HTTP;
|
|
22
25
|
else if (mode.toLowerCase() === 'ftp') this.dataCollectionMode = this.DataCollectionModes.FTP;
|
|
@@ -141,15 +144,23 @@ class BrotherHTTPConfig {
|
|
|
141
144
|
|
|
142
145
|
/** Configuration indicates how to collect data:
|
|
143
146
|
* HTTP only,
|
|
144
|
-
* FTP only (default),
|
|
147
|
+
* FTP only (default for device brother-ftp),
|
|
145
148
|
* HTTP + FTP for program headers
|
|
146
149
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
const isBrotherFtp = config.device === 'brother-ftp';
|
|
151
|
+
let mode = config['collection-mode'];
|
|
152
|
+
if (mode === undefined || mode === null || mode === '') {
|
|
153
|
+
mode = isBrotherFtp ? 'ftp' : '';
|
|
154
|
+
}
|
|
155
|
+
if (!_.isString(mode)) {
|
|
156
|
+
throw new ConfigError('collection-mode must be a string').atAttribute('collection-mode');
|
|
157
|
+
}
|
|
158
|
+
mode = mode.toLowerCase();
|
|
159
|
+
if (isBrotherFtp && mode !== 'ftp') {
|
|
160
|
+
throw new ConfigError('device brother-ftp requires collection-mode ftp').atAttribute('collection-mode');
|
|
150
161
|
}
|
|
151
162
|
if (!_.includes(['http', 'ftp', 'mixed'], mode)) {
|
|
152
|
-
throw new ConfigError('collection-mode must be one of: {http, ftp, mixed}').atAttribute('
|
|
163
|
+
throw new ConfigError('collection-mode must be one of: {http, ftp, mixed}').atAttribute('collection-mode');
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
let keysThatAlreadyExist = '';
|
|
@@ -191,6 +202,36 @@ class BrotherHTTPConfig {
|
|
|
191
202
|
throw new ConfigError(`The value of macros key ${key} must be a valid integer`).atAttribute(key);
|
|
192
203
|
}
|
|
193
204
|
});
|
|
205
|
+
|
|
206
|
+
this.assignOptionalRelativePath(config, 'memory-path', 'memoryPath');
|
|
207
|
+
this.assignOptionalRelativePath(config, 'production-path', 'productionPath');
|
|
208
|
+
this.assignOptionalRelativePath(config, 'panel-path', 'panelPath');
|
|
209
|
+
this.assignOptionalRelativePath(config, 'position-path', 'positionPath');
|
|
210
|
+
this.assignOptionalRelativePath(config, 'work-counter-path', 'workCounterPath');
|
|
211
|
+
this.assignOptionalRelativePath(config, 'alarm-path', 'alarmPath');
|
|
212
|
+
this.assignOptionalRelativePath(config, 'macro-path', 'macroPath');
|
|
213
|
+
|
|
214
|
+
if (_.has(config, 'cnc-version')) {
|
|
215
|
+
const cncVersion = config['cnc-version'];
|
|
216
|
+
if (cncVersion !== 'legacy' && cncVersion !== 'd00') {
|
|
217
|
+
throw new ConfigError('cnc-version must be one of: legacy, d00').atAttribute('cnc-version');
|
|
218
|
+
}
|
|
219
|
+
this.cncVersion = cncVersion;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
assignOptionalRelativePath(config, yamlKey, propertyName) {
|
|
224
|
+
if (!_.has(config, yamlKey)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const value = config[yamlKey];
|
|
228
|
+
if (!_.isString(value) || value.trim() === '') {
|
|
229
|
+
throw new ConfigError(`${yamlKey} must be a non-empty string`).atAttribute(yamlKey);
|
|
230
|
+
}
|
|
231
|
+
if (value.includes('..')) {
|
|
232
|
+
throw new ConfigError(`${yamlKey} must not contain '..'`).atAttribute(yamlKey);
|
|
233
|
+
}
|
|
234
|
+
this[propertyName] = value;
|
|
194
235
|
}
|
|
195
236
|
|
|
196
237
|
checkScanInterval(config) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const ConfigError = require('./configError');
|
|
5
5
|
const TransformConfigUtil = require('./transformConfigUtil');
|
|
6
|
+
const { preprocessBranchTransforms } = require('./branchPreprocessor');
|
|
6
7
|
|
|
7
8
|
class EngineConfig {
|
|
8
9
|
constructor(expressionService, config = {}, device = {}) {
|
|
@@ -48,6 +49,10 @@ class EngineConfig {
|
|
|
48
49
|
return variable;
|
|
49
50
|
});
|
|
50
51
|
|
|
52
|
+
this.config.variables = _.mapValues(this.config.variables, (variable, name) => {
|
|
53
|
+
return preprocessBranchTransforms(variable, `variables.${name}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
51
56
|
_.each(this.getExpressions(this.config), name => expressionService.addExpression(name));
|
|
52
57
|
_.each(this.getStringExpressions(this.config), name => expressionService.addStringExpression(name));
|
|
53
58
|
}
|
package/lib/config/index.js
CHANGED
|
@@ -204,6 +204,7 @@ class Config {
|
|
|
204
204
|
return new OPCUAConfig(expressionService);
|
|
205
205
|
case 'brother':
|
|
206
206
|
case 'brother-http':
|
|
207
|
+
case 'brother-ftp':
|
|
207
208
|
return new BrotherHTTPConfig(expressionService);
|
|
208
209
|
case 'adam-6052':
|
|
209
210
|
return new AdamConfig(expressionService);
|
|
@@ -315,6 +316,7 @@ class Config {
|
|
|
315
316
|
'opc-ua',
|
|
316
317
|
'brother',
|
|
317
318
|
'brother-http',
|
|
319
|
+
'brother-ftp',
|
|
318
320
|
'adam-6052',
|
|
319
321
|
'mtconnect',
|
|
320
322
|
'mtconnect-haas',
|
package/lib/expressionService.js
CHANGED
|
@@ -259,7 +259,7 @@ class ExpressionService {
|
|
|
259
259
|
if (tokens) {
|
|
260
260
|
const first = tokens[0];
|
|
261
261
|
if (first === 'this' || this.channelsLookup[first]) {
|
|
262
|
-
|
|
262
|
+
const nested = _.reduceRight(_.tail(tokens), (sub, item) => {
|
|
263
263
|
if (_.isNumber(item)) {
|
|
264
264
|
return [
|
|
265
265
|
..._.times(Math.max(0, item - 1), () => 0),
|
|
@@ -268,6 +268,11 @@ class ExpressionService {
|
|
|
268
268
|
}
|
|
269
269
|
return { [item]: sub };
|
|
270
270
|
}, 0);
|
|
271
|
+
if (_.isArray(nested) || _.isArray(accum[first])) {
|
|
272
|
+
accum[first] = nested;
|
|
273
|
+
} else {
|
|
274
|
+
accum[first] = _.merge(accum[first] || {}, nested);
|
|
275
|
+
}
|
|
271
276
|
} else {
|
|
272
277
|
accum[first] = 0;
|
|
273
278
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
class BranchChain {
|
|
7
|
+
constructor(chain) {
|
|
8
|
+
this.chain = chain;
|
|
9
|
+
this.opResult = null;
|
|
10
|
+
|
|
11
|
+
const end = this.chain.chainEnd();
|
|
12
|
+
end.on('update', (value) => {
|
|
13
|
+
this.opResult = value;
|
|
14
|
+
});
|
|
15
|
+
end.on('unavailable', () => {
|
|
16
|
+
this.opResult = null;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
update(context, value, time) {
|
|
21
|
+
this.chain.update(context, value, time);
|
|
22
|
+
return this.opResult;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
probe(context, time) {
|
|
26
|
+
this.chain.probe(context, time);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
probeOrForward(context, time) {
|
|
30
|
+
this.chain.probeOrForward(context, time);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setUnavailable(context, time) {
|
|
34
|
+
this.chain.setUnavailable(context, time);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Conditional branching transform. Evaluates conditions in sequence and runs the first
|
|
40
|
+
* matching branch's transform chain. Analogous to if/else-if/else.
|
|
41
|
+
*
|
|
42
|
+
* Config format (after preprocessing):
|
|
43
|
+
* - branch:
|
|
44
|
+
* - if:
|
|
45
|
+
* - condition: this == 1 or x > y
|
|
46
|
+
* - op1
|
|
47
|
+
* - op2
|
|
48
|
+
* - else-if:
|
|
49
|
+
* - condition: this == 2
|
|
50
|
+
* - op1
|
|
51
|
+
* - else:
|
|
52
|
+
* - op1
|
|
53
|
+
*/
|
|
54
|
+
class BranchFilter extends TransformState {
|
|
55
|
+
constructor({ engine, args: { conditions, branchChains, elseChain } }, builder) {
|
|
56
|
+
super();
|
|
57
|
+
|
|
58
|
+
this.engine = engine;
|
|
59
|
+
this.conditions = conditions;
|
|
60
|
+
this.branchChains = _.map(branchChains, (transforms) => {
|
|
61
|
+
const chain = builder.createTransformChain(engine, transforms);
|
|
62
|
+
return new BranchChain(chain);
|
|
63
|
+
});
|
|
64
|
+
this.elseChain = elseChain
|
|
65
|
+
? new BranchChain(builder.createTransformChain(engine, elseChain))
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static op = 'branch';
|
|
70
|
+
|
|
71
|
+
static create(args) {
|
|
72
|
+
return new BranchFilter(args, args.builder);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static parseConfig(configUtil, defn) {
|
|
76
|
+
const components = defn.args;
|
|
77
|
+
if (!_.isArray(components)) {
|
|
78
|
+
configUtil.throwConfigError('branch must contain a list of if/else-if/else components', this.op);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const conditions = [];
|
|
82
|
+
const branchChains = [];
|
|
83
|
+
let elseChain = null;
|
|
84
|
+
let lastWasConditional = false;
|
|
85
|
+
|
|
86
|
+
components.forEach((comp, i) => {
|
|
87
|
+
if (!_.isObject(comp) || _.isArray(comp)) {
|
|
88
|
+
configUtil.throwConfigError('Invalid branch component', this.op);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const key = _.first(_.keys(comp));
|
|
92
|
+
const body = comp[key];
|
|
93
|
+
|
|
94
|
+
if (!_.isArray(body)) {
|
|
95
|
+
configUtil.throwConfigError(`${key} must contain a list of transforms`, this.op);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (key === 'if' || key === 'else-if') {
|
|
99
|
+
if (body.length === 0) {
|
|
100
|
+
configUtil.throwConfigError(`${key} must have at least a condition`, this.op);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const first = body[0];
|
|
104
|
+
let conditionExpr = null;
|
|
105
|
+
if (_.isObject(first) && !_.isArray(first) && _.has(first, 'condition')) {
|
|
106
|
+
conditionExpr = first.condition;
|
|
107
|
+
if (_.isObject(conditionExpr) && _.has(conditionExpr, 'expression')) {
|
|
108
|
+
conditionExpr = conditionExpr.expression;
|
|
109
|
+
}
|
|
110
|
+
conditionExpr = conditionExpr.toString();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!conditionExpr) {
|
|
114
|
+
configUtil.throwConfigError(`${key} first element must be condition: <expression>`, this.op);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const compiledExpression = configUtil.compileExpression(conditionExpr, this.op, { this: 0 });
|
|
118
|
+
conditions.push({
|
|
119
|
+
expression: conditionExpr,
|
|
120
|
+
compiledExpression,
|
|
121
|
+
expressionSources: configUtil.expressionService.expressionTriggers(`${defn.path}.${i}.condition`),
|
|
122
|
+
hasThis: configUtil.expressionService.findName(conditionExpr, 'this'),
|
|
123
|
+
sourceCache: {},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const chainTransforms = body.slice(1);
|
|
127
|
+
const { transform } = configUtil.loadTransformList(chainTransforms, `${defn.path}.${i}.${key}`, false);
|
|
128
|
+
branchChains.push(transform);
|
|
129
|
+
lastWasConditional = true;
|
|
130
|
+
} else if (key === 'else') {
|
|
131
|
+
if (!lastWasConditional) {
|
|
132
|
+
configUtil.throwConfigError('else must follow if or else-if', this.op);
|
|
133
|
+
}
|
|
134
|
+
if (elseChain !== null) {
|
|
135
|
+
configUtil.throwConfigError('only one else allowed per branch', this.op);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { transform } = configUtil.loadTransformList(body, `${defn.path}.${i}.else`, false);
|
|
139
|
+
elseChain = transform;
|
|
140
|
+
lastWasConditional = false;
|
|
141
|
+
} else {
|
|
142
|
+
configUtil.throwConfigError(`Unknown branch component: ${key}`, this.op);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
defn.args = {
|
|
147
|
+
conditions,
|
|
148
|
+
branchChains,
|
|
149
|
+
elseChain,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static getExpressions(configUtil, body, info) {
|
|
154
|
+
const { varName, index, attribute } = info;
|
|
155
|
+
const parts = [];
|
|
156
|
+
|
|
157
|
+
if (!_.isArray(body)) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_.each(body, (comp, compIndex) => {
|
|
162
|
+
if (!_.isObject(comp) || _.isArray(comp)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const key = _.first(_.keys(comp));
|
|
167
|
+
const compBody = comp[key];
|
|
168
|
+
|
|
169
|
+
if (key === 'if' || key === 'else-if') {
|
|
170
|
+
if (_.isArray(compBody) && compBody.length > 0) {
|
|
171
|
+
const first = compBody[0];
|
|
172
|
+
if (_.isObject(first) && _.has(first, 'condition')) {
|
|
173
|
+
const expr = first.condition?.toString?.() ?? first.condition;
|
|
174
|
+
if (expr) {
|
|
175
|
+
parts.push({
|
|
176
|
+
path: `variables.${varName}.${index}.${attribute}.${compIndex}.condition`,
|
|
177
|
+
expression: expr,
|
|
178
|
+
}, {
|
|
179
|
+
path: `variables.${varName}`,
|
|
180
|
+
expression: expr,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (key === 'if' || key === 'else-if' || key === 'else') {
|
|
188
|
+
const chainTransforms = key === 'else'
|
|
189
|
+
? compBody
|
|
190
|
+
: compBody.slice(1);
|
|
191
|
+
const normalized = _.map(chainTransforms, (t) => (_.isString(t) ? { [t]: [] } : t));
|
|
192
|
+
// Include key (if/else-if/else) so expression paths match loadTransformList (used by parseConfig)
|
|
193
|
+
const chainVarName = `${varName}.${index}.${attribute}.${compIndex}.${key}`;
|
|
194
|
+
const chainExprs = configUtil.getExpressionsFromChain(
|
|
195
|
+
normalized,
|
|
196
|
+
chainVarName,
|
|
197
|
+
[...(info.ancestorVars || []), varName]
|
|
198
|
+
);
|
|
199
|
+
parts.push(..._.flatten(chainExprs));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return parts;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
supportsPendingChanges() {
|
|
207
|
+
const chains = [...this.branchChains, this.elseChain].filter(Boolean);
|
|
208
|
+
return _.some(chains, (c) => c.chain.chainSupportsPendingChange?.());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
selectBranch(context, value, time) {
|
|
212
|
+
for (let i = 0; i < this.conditions.length; i += 1) {
|
|
213
|
+
const rule = this.conditions[i];
|
|
214
|
+
|
|
215
|
+
if (rule.hasThis) {
|
|
216
|
+
rule.sourceCache.this = value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const unavailTerm = _.reduce(rule.expressionSources, (res, name) => {
|
|
220
|
+
const state = this.engine.getState(name);
|
|
221
|
+
if (state) {
|
|
222
|
+
rule.sourceCache[name] = state.value;
|
|
223
|
+
}
|
|
224
|
+
return res || !state || !state.available;
|
|
225
|
+
}, false);
|
|
226
|
+
|
|
227
|
+
if (unavailTerm) {
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
if (rule.compiledExpression.evaluate(rule.sourceCache)) {
|
|
233
|
+
return i;
|
|
234
|
+
}
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.recordError(context, err, time);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return this.elseChain !== null ? this.conditions.length : -1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
update(context, value, time) {
|
|
244
|
+
if (value === 'UNAVAILABLE') {
|
|
245
|
+
this.setUnavailable(context, time);
|
|
246
|
+
} else {
|
|
247
|
+
super.update(context, value, time);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
filter(context, value, time) {
|
|
252
|
+
const branchIndex = this.selectBranch(context, value, time);
|
|
253
|
+
|
|
254
|
+
if (branchIndex < 0) {
|
|
255
|
+
this.commitValue(context, value, time);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const chain = branchIndex < this.branchChains.length ? this.branchChains[branchIndex] : this.elseChain;
|
|
260
|
+
|
|
261
|
+
const result = chain.update(context, value, time);
|
|
262
|
+
if (result !== null && result !== undefined) {
|
|
263
|
+
this.commitValue(context, result, time);
|
|
264
|
+
} else {
|
|
265
|
+
this.setUnavailable(context, time);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
setUnavailable(context, time) {
|
|
270
|
+
super.setUnavailable(context, time);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = BranchFilter;
|
package/lib/transform/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const AccumulateFilter = require('./accumulate');
|
|
4
4
|
const AverageFilter = require('./average');
|
|
5
|
+
const BranchFilter = require('./branch');
|
|
5
6
|
const DebounceFilter = require('./debounce');
|
|
6
7
|
const DownsampleFilter = require('./downsample');
|
|
7
8
|
const EdgeFilter = require('./edge');
|
|
@@ -48,6 +49,7 @@ const WindowCountFilter = require('./windowCount');
|
|
|
48
49
|
module.exports = {
|
|
49
50
|
accumulate: AccumulateFilter,
|
|
50
51
|
average: AverageFilter,
|
|
52
|
+
branch: BranchFilter,
|
|
51
53
|
debounce: DebounceFilter,
|
|
52
54
|
downsample: DownsampleFilter,
|
|
53
55
|
edge: EdgeFilter,
|
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect;
|
|
4
|
+
const ConfigError = require('../../lib/config/configError');
|
|
5
|
+
const testUtils = require('../util/testUtils');
|
|
6
|
+
|
|
7
|
+
describe('Brother FTP device config', function () {
|
|
8
|
+
it('accepts brother-ftp without collection-mode (defaults to ftp)', async function () {
|
|
9
|
+
const cfg = await testUtils.loadConfig('device/brother-ftp-minimal.yml');
|
|
10
|
+
expect(cfg.deviceName).to.eq('brother-ftp');
|
|
11
|
+
expect(cfg.device.dataCollectionMode).to.eq(cfg.device.DataCollectionModes.FTP);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('passes through optional FTP paths and cnc-version', async function () {
|
|
15
|
+
const cfg = await testUtils.loadConfig('device/brother-ftp-paths.yml');
|
|
16
|
+
expect(cfg.device.cncVersion).to.eq('d00');
|
|
17
|
+
expect(cfg.device.memoryPath).to.eq('_DAT/MEM.NC');
|
|
18
|
+
expect(cfg.device.productionPath).to.eq('_PRD/PRDD2.NC');
|
|
19
|
+
expect(cfg.device.panelPath).to.eq('_DAT/PANEL.NC');
|
|
20
|
+
expect(cfg.device.positionPath).to.eq('_DAT/PDSP.NC');
|
|
21
|
+
expect(cfg.device.workCounterPath).to.eq('_DAT/WKCNTR.NC');
|
|
22
|
+
expect(cfg.device.alarmPath).to.eq('_DAT/ALARM.NC');
|
|
23
|
+
expect(cfg.device.macroPath).to.eq('_DAT/MCRNI1.NC');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('does not set path properties when keys are omitted', async function () {
|
|
27
|
+
const cfg = await testUtils.loadConfig('device/brother-ftp-minimal.yml');
|
|
28
|
+
expect(cfg.device.memoryPath).to.eq(undefined);
|
|
29
|
+
expect(cfg.device.cncVersion).to.eq(undefined);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('accepts cnc-version legacy', async function () {
|
|
33
|
+
const cfg = await testUtils.loadConfig('device/brother-ftp-cnc-legacy.yml');
|
|
34
|
+
expect(cfg.device.cncVersion).to.eq('legacy');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('rejects invalid cnc-version', async function () {
|
|
38
|
+
try {
|
|
39
|
+
await testUtils.loadConfig('device/brother-ftp-bad-cnc.yml');
|
|
40
|
+
expect.fail('Expected ConfigError');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('rejects paths containing ..', async function () {
|
|
47
|
+
try {
|
|
48
|
+
await testUtils.loadConfig('device/brother-ftp-bad-path.yml');
|
|
49
|
+
expect.fail('Expected ConfigError');
|
|
50
|
+
} catch (err) {
|
|
51
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects brother-ftp with non-ftp collection-mode', async function () {
|
|
56
|
+
try {
|
|
57
|
+
await testUtils.loadConfig('device/brother-ftp-bad-mode.yml');
|
|
58
|
+
expect.fail('Expected ConfigError');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -15,5 +15,7 @@ describe('Brother HTTP config tests', function () {
|
|
|
15
15
|
expect(defaultConfig.device.port).to.eq(21);
|
|
16
16
|
expect(defaultConfig.device.scanInterval).to.eq(1000);
|
|
17
17
|
expect(defaultConfig.device.httpInterval).to.eq(50);
|
|
18
|
+
expect(defaultConfig.device.memoryPath).to.eq(undefined);
|
|
19
|
+
expect(defaultConfig.device.cncVersion).to.eq(undefined);
|
|
18
20
|
});
|
|
19
21
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
device: brother-ftp
|
|
2
|
+
endpoint: 192.168.1.50
|
|
3
|
+
collection-mode: ftp
|
|
4
|
+
cnc-version: d00
|
|
5
|
+
memory-path: _DAT/MEM.NC
|
|
6
|
+
production-path: _PRD/PRDD2.NC
|
|
7
|
+
panel-path: _DAT/PANEL.NC
|
|
8
|
+
position-path: _DAT/PDSP.NC
|
|
9
|
+
work-counter-path: _DAT/WKCNTR.NC
|
|
10
|
+
alarm-path: _DAT/ALARM.NC
|
|
11
|
+
macro-path: _DAT/MCRNI1.NC
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
device: mtconnect-adapter
|
|
3
|
+
endpoint: localhost:8001
|
|
4
|
+
mtconnect-port: 8002
|
|
5
|
+
declare-keys:
|
|
6
|
+
- counter
|
|
7
|
+
- x
|
|
8
|
+
- y
|
|
9
|
+
- z
|
|
10
|
+
- obj
|
|
11
|
+
variables:
|
|
12
|
+
branch1:
|
|
13
|
+
- source: counter
|
|
14
|
+
- if:
|
|
15
|
+
- condition: this == 1
|
|
16
|
+
- expression: 100
|
|
17
|
+
- else-if:
|
|
18
|
+
- condition: this == 2
|
|
19
|
+
- expression: 200
|
|
20
|
+
- else:
|
|
21
|
+
- expression: 0
|
|
22
|
+
branch2simple:
|
|
23
|
+
- source: counter
|
|
24
|
+
- if:
|
|
25
|
+
- condition: this == 1
|
|
26
|
+
- expression: 10
|
|
27
|
+
- else:
|
|
28
|
+
- expression: 0
|
|
29
|
+
branch3:
|
|
30
|
+
- source: counter
|
|
31
|
+
- if:
|
|
32
|
+
- condition: x > y
|
|
33
|
+
- expression: 1
|
|
34
|
+
- else-if:
|
|
35
|
+
- condition: x < y
|
|
36
|
+
- expression: -1
|
|
37
|
+
- else:
|
|
38
|
+
- expression: 0
|
|
39
|
+
ifOnly:
|
|
40
|
+
- source: counter
|
|
41
|
+
- if:
|
|
42
|
+
- condition: this == 1
|
|
43
|
+
- expression: 100
|
|
44
|
+
ifElseIf:
|
|
45
|
+
- source: counter
|
|
46
|
+
- if:
|
|
47
|
+
- condition: this == 1
|
|
48
|
+
- expression: 100
|
|
49
|
+
- else-if:
|
|
50
|
+
- condition: this == 2
|
|
51
|
+
- expression: 200
|
|
52
|
+
twoElseIfs:
|
|
53
|
+
- source: counter
|
|
54
|
+
- if:
|
|
55
|
+
- condition: this == 1
|
|
56
|
+
- expression: 10
|
|
57
|
+
- else-if:
|
|
58
|
+
- condition: this == 2
|
|
59
|
+
- expression: 20
|
|
60
|
+
- else-if:
|
|
61
|
+
- condition: this == 3
|
|
62
|
+
- expression: 30
|
|
63
|
+
- else:
|
|
64
|
+
- expression: 0
|
|
65
|
+
ifElseIfConsecutive:
|
|
66
|
+
- source: counter
|
|
67
|
+
- if:
|
|
68
|
+
- condition: this == 1
|
|
69
|
+
- expression: 100
|
|
70
|
+
- else:
|
|
71
|
+
- expression: 2
|
|
72
|
+
- if:
|
|
73
|
+
- condition: counter == 2
|
|
74
|
+
- expression: 200
|
|
75
|
+
branchWithThis:
|
|
76
|
+
- source: counter
|
|
77
|
+
- if:
|
|
78
|
+
- condition: this == 1
|
|
79
|
+
- expression: 100
|
|
80
|
+
- else:
|
|
81
|
+
- expression: this
|
|
82
|
+
branchUnavailable:
|
|
83
|
+
- source: counter
|
|
84
|
+
- if:
|
|
85
|
+
- condition: this == 1
|
|
86
|
+
- expression: x + 1
|
|
87
|
+
- else:
|
|
88
|
+
- expression: this
|
|
89
|
+
branchWithInnerVar:
|
|
90
|
+
- source: counter
|
|
91
|
+
- if:
|
|
92
|
+
- condition: obj.a > 5
|
|
93
|
+
- expression: obj.a
|
|
94
|
+
- else:
|
|
95
|
+
- expression: obj.b
|
|
96
|
+
nested:
|
|
97
|
+
- source: counter
|
|
98
|
+
- if:
|
|
99
|
+
- condition: this > 0
|
|
100
|
+
- if:
|
|
101
|
+
- condition: this > 2
|
|
102
|
+
- expression: z
|
|
103
|
+
- else:
|
|
104
|
+
- expression: y
|
|
105
|
+
- else:
|
|
106
|
+
- expression: x
|
|
107
|
+
nestedInnerVar:
|
|
108
|
+
- source: counter
|
|
109
|
+
- if:
|
|
110
|
+
- condition: this > 0
|
|
111
|
+
- if:
|
|
112
|
+
- condition: obj.a > obj.b
|
|
113
|
+
- expression: obj.a + obj.b
|
|
114
|
+
- else:
|
|
115
|
+
- expression: y
|
|
116
|
+
- else:
|
|
117
|
+
- expression: x
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
device: mtconnect-adapter
|
|
3
|
+
endpoint: localhost:8001
|
|
4
|
+
mtconnect-port: 8002
|
|
5
|
+
declare-keys:
|
|
6
|
+
- counter
|
|
7
|
+
variables:
|
|
8
|
+
badVar:
|
|
9
|
+
- source: counter
|
|
10
|
+
- if:
|
|
11
|
+
- condition: this == 1
|
|
12
|
+
- expression: 100
|
|
13
|
+
- invert
|
|
14
|
+
- else-if:
|
|
15
|
+
- condition: this == 2
|
|
16
|
+
- expression: 200
|
|
17
|
+
- else:
|
|
18
|
+
- expression: 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect;
|
|
4
|
+
const ConfigError = require('../../lib/config/configError');
|
|
5
|
+
const testUtils = require('../util/testUtils');
|
|
6
|
+
|
|
7
|
+
describe('branch transform invalid config tests', function () {
|
|
8
|
+
it('rejects else by itself', async function () {
|
|
9
|
+
try {
|
|
10
|
+
await testUtils.loadConfig('transform/invalid-branch-else-only.yml');
|
|
11
|
+
expect.fail('Config with else by itself should have thrown');
|
|
12
|
+
} catch (err) {
|
|
13
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
14
|
+
expect(err.message).to.include('else');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('rejects else-if by itself', async function () {
|
|
19
|
+
try {
|
|
20
|
+
await testUtils.loadConfig('transform/invalid-branch-elseif-only.yml');
|
|
21
|
+
expect.fail('Config with else-if by itself should have thrown');
|
|
22
|
+
} catch (err) {
|
|
23
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
24
|
+
expect(err.message).to.include('else-if');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('rejects if without condition as first transform', async function () {
|
|
29
|
+
try {
|
|
30
|
+
await testUtils.loadConfig('transform/invalid-branch-if-no-condition.yml');
|
|
31
|
+
expect.fail('Config with if without condition should have thrown');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
34
|
+
expect(err.message).to.include('condition');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects else-if without condition as first transform', async function () {
|
|
39
|
+
try {
|
|
40
|
+
await testUtils.loadConfig('transform/invalid-branch-elseif-no-condition.yml');
|
|
41
|
+
expect.fail('Config with else-if without condition should have thrown');
|
|
42
|
+
} catch (err) {
|
|
43
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
44
|
+
expect(err.message).to.include('condition');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('rejects unrelated op between if/else-if/else', async function () {
|
|
49
|
+
try {
|
|
50
|
+
await testUtils.loadConfig('transform/invalid-branch-unrelated-op.yml');
|
|
51
|
+
expect.fail('Config with unrelated op between if/else-if/else should have thrown');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
expect(err).to.be.instanceof(ConfigError);
|
|
54
|
+
expect(err.message).to.match(/else-if|else|Unsupported/);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect;
|
|
4
|
+
const EngineV2 = require('../../lib/engine/engineV2');
|
|
5
|
+
const Builder = require('../../lib/engine/transformBuilderV2');
|
|
6
|
+
const testUtils = require('../util/testUtils');
|
|
7
|
+
|
|
8
|
+
describe('branch transform tests', function () {
|
|
9
|
+
let config;
|
|
10
|
+
before(async () => {
|
|
11
|
+
config = await testUtils.loadConfig('transform/branch.yml');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('selects if branch when condition matches', function () {
|
|
15
|
+
const engine = new EngineV2(config);
|
|
16
|
+
const builder = new Builder(config);
|
|
17
|
+
builder.build(engine);
|
|
18
|
+
|
|
19
|
+
const source = testUtils.valueSource();
|
|
20
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch1, source);
|
|
21
|
+
|
|
22
|
+
source.sendValue('counter', 1, 0);
|
|
23
|
+
|
|
24
|
+
engine.validateFilter([
|
|
25
|
+
[100, 0],
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('selects else-if branch when condition matches', function () {
|
|
30
|
+
const engine = new EngineV2(config);
|
|
31
|
+
const builder = new Builder(config);
|
|
32
|
+
builder.build(engine);
|
|
33
|
+
|
|
34
|
+
const source = testUtils.valueSource();
|
|
35
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch1, source);
|
|
36
|
+
|
|
37
|
+
source.sendValue('counter', 2, 0);
|
|
38
|
+
|
|
39
|
+
engine.validateFilter([
|
|
40
|
+
[200, 0],
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('selects else branch when no condition matches', function () {
|
|
45
|
+
const engine = new EngineV2(config);
|
|
46
|
+
const builder = new Builder(config);
|
|
47
|
+
builder.build(engine);
|
|
48
|
+
|
|
49
|
+
const source = testUtils.valueSource();
|
|
50
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch1, source);
|
|
51
|
+
|
|
52
|
+
source.sendValue('counter', 5, 0);
|
|
53
|
+
|
|
54
|
+
engine.validateFilter([
|
|
55
|
+
[0, 0],
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('evaluates constant expression in branch', function () {
|
|
60
|
+
const engine = new EngineV2(config);
|
|
61
|
+
const builder = new Builder(config);
|
|
62
|
+
builder.build(engine);
|
|
63
|
+
|
|
64
|
+
const source = testUtils.valueSource();
|
|
65
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch2simple, source);
|
|
66
|
+
|
|
67
|
+
source.sendValue('counter', 1, 0);
|
|
68
|
+
|
|
69
|
+
engine.validateFilter([
|
|
70
|
+
[10, 0],
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('uses external variables in conditions', function () {
|
|
75
|
+
const engine = new EngineV2(config);
|
|
76
|
+
const builder = new Builder(config);
|
|
77
|
+
builder.build(engine);
|
|
78
|
+
|
|
79
|
+
const source = testUtils.valueSource();
|
|
80
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch3, source);
|
|
81
|
+
|
|
82
|
+
source.sendValue('x', 10, 0);
|
|
83
|
+
source.sendValue('y', 5, 0);
|
|
84
|
+
source.sendValue('counter', 0, 0);
|
|
85
|
+
|
|
86
|
+
engine.validateFilter([
|
|
87
|
+
[1, 0],
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('selects else-if when x < y', function () {
|
|
92
|
+
const engine = new EngineV2(config);
|
|
93
|
+
const builder = new Builder(config);
|
|
94
|
+
builder.build(engine);
|
|
95
|
+
|
|
96
|
+
const source = testUtils.valueSource();
|
|
97
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch3, source);
|
|
98
|
+
|
|
99
|
+
source.sendValue('x', 5, 0);
|
|
100
|
+
source.sendValue('y', 10, 0);
|
|
101
|
+
source.sendValue('counter', 0, 0);
|
|
102
|
+
|
|
103
|
+
engine.validateFilter([
|
|
104
|
+
[-1, 0],
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('selects else when x == y', function () {
|
|
109
|
+
const engine = new EngineV2(config);
|
|
110
|
+
const builder = new Builder(config);
|
|
111
|
+
builder.build(engine);
|
|
112
|
+
|
|
113
|
+
const source = testUtils.valueSource();
|
|
114
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branch3, source);
|
|
115
|
+
|
|
116
|
+
source.sendValue('x', 5, 0);
|
|
117
|
+
source.sendValue('y', 5, 0);
|
|
118
|
+
source.sendValue('counter', 0, 0);
|
|
119
|
+
|
|
120
|
+
engine.validateFilter([
|
|
121
|
+
[0, 0],
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('if only: selects branch when condition matches', function () {
|
|
126
|
+
const engine = new EngineV2(config);
|
|
127
|
+
const builder = new Builder(config);
|
|
128
|
+
builder.build(engine);
|
|
129
|
+
|
|
130
|
+
const source = testUtils.valueSource();
|
|
131
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifOnly, source);
|
|
132
|
+
|
|
133
|
+
source.sendValue('counter', 1, 0);
|
|
134
|
+
|
|
135
|
+
engine.validateFilter([
|
|
136
|
+
[100, 0],
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('if only: passes through when condition does not match', function () {
|
|
141
|
+
const engine = new EngineV2(config);
|
|
142
|
+
const builder = new Builder(config);
|
|
143
|
+
builder.build(engine);
|
|
144
|
+
|
|
145
|
+
const source = testUtils.valueSource();
|
|
146
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifOnly, source);
|
|
147
|
+
|
|
148
|
+
source.sendValue('counter', 5, 0);
|
|
149
|
+
|
|
150
|
+
engine.validateFilter([
|
|
151
|
+
[5, 0],
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('if/else-if without else: selects first branch', function () {
|
|
156
|
+
const engine = new EngineV2(config);
|
|
157
|
+
const builder = new Builder(config);
|
|
158
|
+
builder.build(engine);
|
|
159
|
+
|
|
160
|
+
const source = testUtils.valueSource();
|
|
161
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifElseIf, source);
|
|
162
|
+
|
|
163
|
+
source.sendValue('counter', 1, 0);
|
|
164
|
+
|
|
165
|
+
engine.validateFilter([
|
|
166
|
+
[100, 0],
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('if/else-if without else: selects second branch', function () {
|
|
171
|
+
const engine = new EngineV2(config);
|
|
172
|
+
const builder = new Builder(config);
|
|
173
|
+
builder.build(engine);
|
|
174
|
+
|
|
175
|
+
const source = testUtils.valueSource();
|
|
176
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifElseIf, source);
|
|
177
|
+
|
|
178
|
+
source.sendValue('counter', 2, 0);
|
|
179
|
+
|
|
180
|
+
engine.validateFilter([
|
|
181
|
+
[200, 0],
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('if/else-if without else: passes through when neither matches', function () {
|
|
186
|
+
const engine = new EngineV2(config);
|
|
187
|
+
const builder = new Builder(config);
|
|
188
|
+
builder.build(engine);
|
|
189
|
+
|
|
190
|
+
const source = testUtils.valueSource();
|
|
191
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifElseIf, source);
|
|
192
|
+
|
|
193
|
+
source.sendValue('counter', 5, 0);
|
|
194
|
+
|
|
195
|
+
engine.validateFilter([
|
|
196
|
+
[5, 0],
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('two else-ifs: selects each branch correctly', function () {
|
|
201
|
+
const engine = new EngineV2(config);
|
|
202
|
+
const builder = new Builder(config);
|
|
203
|
+
builder.build(engine);
|
|
204
|
+
|
|
205
|
+
const source = testUtils.valueSource();
|
|
206
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.twoElseIfs, source);
|
|
207
|
+
|
|
208
|
+
source.sendValue('counter', 1, 0);
|
|
209
|
+
source.sendValue('counter', 2, 1);
|
|
210
|
+
source.sendValue('counter', 3, 2);
|
|
211
|
+
source.sendValue('counter', 5, 3);
|
|
212
|
+
|
|
213
|
+
engine.validateFilter([[10, 0], [20, 1], [30, 2], [0, 3]]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('if/else/if: two consecutive branches', function () {
|
|
217
|
+
const engine = new EngineV2(config);
|
|
218
|
+
const builder = new Builder(config);
|
|
219
|
+
builder.build(engine);
|
|
220
|
+
|
|
221
|
+
const source = testUtils.valueSource();
|
|
222
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.ifElseIfConsecutive, source);
|
|
223
|
+
|
|
224
|
+
source.sendValue('counter', 1, 0);
|
|
225
|
+
source.sendValue('counter', 2, 1);
|
|
226
|
+
source.sendValue('counter', 3, 2);
|
|
227
|
+
|
|
228
|
+
engine.validateFilter([[100, 0], [200, 1], [2, 2]]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('expression: this in else branch passes through input value', function () {
|
|
232
|
+
const engine = new EngineV2(config);
|
|
233
|
+
const builder = new Builder(config);
|
|
234
|
+
builder.build(engine);
|
|
235
|
+
|
|
236
|
+
const source = testUtils.valueSource();
|
|
237
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branchWithThis, source);
|
|
238
|
+
|
|
239
|
+
source.sendValue('counter', 1, 0);
|
|
240
|
+
source.sendValue('counter', 5, 1);
|
|
241
|
+
|
|
242
|
+
engine.validateFilter([[100, 0], [5, 1]]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('unavailable in taken branch propagates; unavailable in not-taken branch has no effect', function () {
|
|
246
|
+
const engine = new EngineV2(config);
|
|
247
|
+
const builder = new Builder(config);
|
|
248
|
+
builder.build(engine);
|
|
249
|
+
|
|
250
|
+
const source = testUtils.valueSource();
|
|
251
|
+
engine.addValueSource(source);
|
|
252
|
+
|
|
253
|
+
const filterUpdates = [];
|
|
254
|
+
const endFilter = engine.variablePool.branchUnavailable.chainEnd();
|
|
255
|
+
endFilter.on('update', (value, time) => {
|
|
256
|
+
filterUpdates.push({ time, value });
|
|
257
|
+
});
|
|
258
|
+
endFilter.on('unavailable', (time) => {
|
|
259
|
+
filterUpdates.push({ time, value: 'UNAVAILABLE' });
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const expectFilter = (expected) => {
|
|
263
|
+
const ref = expected.map(([value, time]) => ({ time, value }));
|
|
264
|
+
expect(filterUpdates.length).to.eq(ref.length);
|
|
265
|
+
ref.forEach((r, i) => {
|
|
266
|
+
expect(filterUpdates[i].time).to.be.closeTo(r.time, 0.000001);
|
|
267
|
+
expect(filterUpdates[i].value).to.deep.eq(r.value);
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
source.sendValue('x', 10, 0);
|
|
272
|
+
source.sendValue('counter', 1, 0);
|
|
273
|
+
source.sendValue('x', 'UNAVAILABLE', 1);
|
|
274
|
+
source.sendValue('counter', 5, 2);
|
|
275
|
+
source.sendValue('x', 'UNAVAILABLE', 2);
|
|
276
|
+
|
|
277
|
+
expectFilter([['UNAVAILABLE', 0], [11, 0], ['UNAVAILABLE', 1], ['UNAVAILABLE', 1], [5, 2]]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('nested if/else inside branch', function () {
|
|
281
|
+
const engine = new EngineV2(config);
|
|
282
|
+
const builder = new Builder(config);
|
|
283
|
+
builder.build(engine);
|
|
284
|
+
|
|
285
|
+
const source = testUtils.valueSource();
|
|
286
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.nested, source);
|
|
287
|
+
|
|
288
|
+
source.sendValue('x', 0, 0);
|
|
289
|
+
source.sendValue('y', 150, 0);
|
|
290
|
+
source.sendValue('z', 200, 0);
|
|
291
|
+
|
|
292
|
+
source.sendValue('counter', 0, 0);
|
|
293
|
+
source.sendValue('counter', 1, 1);
|
|
294
|
+
source.sendValue('counter', 2, 2);
|
|
295
|
+
source.sendValue('counter', 3, 3);
|
|
296
|
+
|
|
297
|
+
engine.validateFilter([[0, 0], [150, 1], [150, 2], [200, 3]]);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('nested branch re-evaluates when expression variable in taken path updates', function () {
|
|
301
|
+
const engine = new EngineV2(config);
|
|
302
|
+
const builder = new Builder(config);
|
|
303
|
+
builder.build(engine);
|
|
304
|
+
|
|
305
|
+
const source = testUtils.valueSource();
|
|
306
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.nested, source);
|
|
307
|
+
|
|
308
|
+
source.sendValue('x', 0, 0);
|
|
309
|
+
source.sendValue('y', 150, 0);
|
|
310
|
+
source.sendValue('z', 200, 0);
|
|
311
|
+
|
|
312
|
+
source.sendValue('counter', 0, 0);
|
|
313
|
+
source.sendValue('x', 1, 1);
|
|
314
|
+
source.sendValue('counter', 1, 2);
|
|
315
|
+
source.sendValue('y', 151, 3);
|
|
316
|
+
source.sendValue('counter', 3, 4);
|
|
317
|
+
source.sendValue('z', 201, 5);
|
|
318
|
+
|
|
319
|
+
engine.validateFilter([[0, 0], [1, 1], [150, 2], [151, 3], [200, 4], [201, 5]]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('branchWithInnerVar selects branch by obj.a and emits obj.a or obj.b', function () {
|
|
323
|
+
const engine = new EngineV2(config);
|
|
324
|
+
const builder = new Builder(config);
|
|
325
|
+
builder.build(engine);
|
|
326
|
+
|
|
327
|
+
const source = testUtils.valueSource();
|
|
328
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.branchWithInnerVar, source);
|
|
329
|
+
|
|
330
|
+
source.sendValue('obj', { a: 10, b: 1 }, 0);
|
|
331
|
+
source.sendValue('counter', 1, 0);
|
|
332
|
+
source.sendValue('obj', { a: 3, b: 2 }, 1);
|
|
333
|
+
source.sendValue('counter', 2, 1);
|
|
334
|
+
|
|
335
|
+
engine.validateFilter([[10, 0], [2, 1]]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('nestedInnerVar selects nested branches by this and obj.a, obj.b', function () {
|
|
339
|
+
const engine = new EngineV2(config);
|
|
340
|
+
const builder = new Builder(config);
|
|
341
|
+
builder.build(engine);
|
|
342
|
+
|
|
343
|
+
const source = testUtils.valueSource();
|
|
344
|
+
testUtils.attachEngineTransformValidator(engine, engine.variablePool.nestedInnerVar, source);
|
|
345
|
+
|
|
346
|
+
source.sendValue('x', 0, 0);
|
|
347
|
+
source.sendValue('y', 150, 0);
|
|
348
|
+
source.sendValue('obj', { a: 10, b: 5 }, 0);
|
|
349
|
+
|
|
350
|
+
source.sendValue('counter', 0, 0);
|
|
351
|
+
source.sendValue('counter', 1, 1);
|
|
352
|
+
source.sendValue('obj', { a: 3, b: 8 }, 2);
|
|
353
|
+
source.sendValue('counter', 2, 2);
|
|
354
|
+
|
|
355
|
+
engine.validateFilter([[0, 0], [15, 1], [150, 2]]);
|
|
356
|
+
});
|
|
357
|
+
});
|