@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.
- package/.circleci/config.yml +141 -0
- package/.eslintrc.json +36 -0
- package/.gitattributes +12 -0
- package/CHANGELOG.md +544 -0
- package/README.md +2 -0
- package/index.js +17 -0
- package/lib/config/adapterConfig.js +535 -0
- package/lib/config/common/allowDenyList.js +58 -0
- package/lib/config/common/jsonPath.js +44 -0
- package/lib/config/common/labjackU3T4Common.js +227 -0
- package/lib/config/common/validations.js +142 -0
- package/lib/config/configError.js +32 -0
- package/lib/config/device/adam-6052Config.js +103 -0
- package/lib/config/device/brotherHTTPConfig.js +215 -0
- package/lib/config/device/ethernetIPConfig.js +191 -0
- package/lib/config/device/ifmIotConfig.js +245 -0
- package/lib/config/device/jsonHttpConfig.js +76 -0
- package/lib/config/device/labjackT4Config.js +39 -0
- package/lib/config/device/labjackT7Config.js +192 -0
- package/lib/config/device/labjackU3Config.js +32 -0
- package/lib/config/device/modbusTcpConfig.js +336 -0
- package/lib/config/device/mqttBaseConfig.js +70 -0
- package/lib/config/device/mqttConfig.js +113 -0
- package/lib/config/device/mqttLincolnConfig.js +62 -0
- package/lib/config/device/mtconnectAdapterConfig.js +42 -0
- package/lib/config/device/mtconnectBaseConfig.js +136 -0
- package/lib/config/device/mtconnectConfig.js +52 -0
- package/lib/config/device/mtconnectHaasConfig.js +15 -0
- package/lib/config/device/nullConfig.js +81 -0
- package/lib/config/device/opcuaConfig.js +205 -0
- package/lib/config/device/pcccConfig.js +88 -0
- package/lib/config/engineConfigV1.js +382 -0
- package/lib/config/engineConfigV2.js +106 -0
- package/lib/config/generator/counterGenConfig.js +15 -0
- package/lib/config/generator/cronGenConfig.js +33 -0
- package/lib/config/generator/dateTimeGenConfig.js +33 -0
- package/lib/config/index.js +339 -0
- package/lib/config/transformConfigUtil.js +296 -0
- package/lib/engine/dataOutput.js +357 -0
- package/lib/engine/deviceOutput.js +186 -0
- package/lib/engine/engineV1.js +480 -0
- package/lib/engine/engineV2.js +719 -0
- package/lib/engine/index.js +34 -0
- package/lib/engine/transformBuilderV1.js +111 -0
- package/lib/engine/transformBuilderV2.js +74 -0
- package/lib/expressionService.js +330 -0
- package/lib/math.js +142 -0
- package/lib/transform/accumulate.js +98 -0
- package/lib/transform/average.js +56 -0
- package/lib/transform/debounce.js +152 -0
- package/lib/transform/downsample.js +69 -0
- package/lib/transform/edge.js +34 -0
- package/lib/transform/expression.js +91 -0
- package/lib/transform/fallingEdge.js +70 -0
- package/lib/transform/fromBuffer.js +89 -0
- package/lib/transform/hash.js +41 -0
- package/lib/transform/ignoreValue.js +118 -0
- package/lib/transform/index.js +93 -0
- package/lib/transform/invert.js +25 -0
- package/lib/transform/latch.js +99 -0
- package/lib/transform/latchValue.js +115 -0
- package/lib/transform/logicAnd.js +67 -0
- package/lib/transform/logicOr.js +67 -0
- package/lib/transform/map.js +115 -0
- package/lib/transform/max.js +57 -0
- package/lib/transform/maxLength.js +39 -0
- package/lib/transform/min.js +57 -0
- package/lib/transform/minDelta.js +40 -0
- package/lib/transform/offDelay.js +89 -0
- package/lib/transform/onDelay.js +89 -0
- package/lib/transform/patternEscape.js +24 -0
- package/lib/transform/patternMatch.js +194 -0
- package/lib/transform/patternReplace.js +170 -0
- package/lib/transform/patternTest.js +85 -0
- package/lib/transform/rateOfChange.js +56 -0
- package/lib/transform/reject.js +115 -0
- package/lib/transform/resample.js +96 -0
- package/lib/transform/risingEdge.js +90 -0
- package/lib/transform/risingEdgeCounter.js +179 -0
- package/lib/transform/sampleInterval.js +12 -0
- package/lib/transform/source.js +116 -0
- package/lib/transform/state.js +201 -0
- package/lib/transform/threshold.js +52 -0
- package/lib/transform/toBuffer.js +159 -0
- package/lib/transform/toggle.js +118 -0
- package/lib/transform/transformState.js +193 -0
- package/lib/transform/trim.js +27 -0
- package/lib/transform/util/chainSource.js +96 -0
- package/lib/transform/util/ringBuffer.js +34 -0
- package/lib/transform/valueChange.js +34 -0
- package/lib/transform/valueDecrease.js +38 -0
- package/lib/transform/valueIncrease.js +38 -0
- package/lib/transform/valueIncreaseDiff.js +66 -0
- package/lib/transform/whenUnavailable.js +73 -0
- package/lib/transform/windowCount.js +86 -0
- package/lib/util/fileUtil.js +44 -0
- package/package.json +38 -0
- package/test/.eslintrc.json +15 -0
- package/test/chainedTransform.test.js +88 -0
- package/test/conditions.test.js +118 -0
- package/test/config/ab-pccc.test.js +41 -0
- package/test/config/adam-6052.test.js +16 -0
- package/test/config/adapter.test.js +109 -0
- package/test/config/brother-http.test.js +19 -0
- package/test/config/ethernet-ip.test.js +19 -0
- package/test/config/ifm-iot.test.js +20 -0
- package/test/config/json-http.test.js +47 -0
- package/test/config/labjack-t4.test.js +78 -0
- package/test/config/labjack-t7.test.js +18 -0
- package/test/config/labjack-u3.test.js +17 -0
- package/test/config/modbusTcp.test.js +87 -0
- package/test/config/mqtt.test.js +19 -0
- package/test/config/mqttLincoln.test.js +29 -0
- package/test/config/mtconnect.test.js +63 -0
- package/test/config/mtconnectAdapter.test.js +124 -0
- package/test/config/mtconnectHaas.test.js +15 -0
- package/test/config/null.test.js +16 -0
- package/test/config/opcua.test.js +97 -0
- package/test/config-tests.js +102 -0
- package/test/configFiles/conditions.yml +37 -0
- package/test/configFiles/data-items-legacy.yml +24 -0
- package/test/configFiles/data-items-shorthand.yml +14 -0
- package/test/configFiles/data-items.yml +12 -0
- package/test/configFiles/device/ab-pccc-default.yml +3 -0
- package/test/configFiles/device/ab-pccc-full.yml +13 -0
- package/test/configFiles/device/adam-6052-default.yml +2 -0
- package/test/configFiles/device/brother-http-default.yml +3 -0
- package/test/configFiles/device/ethernet-ip-default.yml +2 -0
- package/test/configFiles/device/ifm-iot-default.yml +2 -0
- package/test/configFiles/device/json-http-bad-prop.yml +13 -0
- package/test/configFiles/device/json-http-bad-prop2.yml +13 -0
- package/test/configFiles/device/json-http-default.yml +3 -0
- package/test/configFiles/device/json-http-std.yml +13 -0
- package/test/configFiles/device/labjack-t4-condition-exprs.yaml +46 -0
- package/test/configFiles/device/labjack-t4-default.yml +3 -0
- package/test/configFiles/device/labjack-t4-pins-alt.yaml +41 -0
- package/test/configFiles/device/labjack-t4-pins.yaml +40 -0
- package/test/configFiles/device/labjack-t4-v1.yaml +29 -0
- package/test/configFiles/device/labjack-t7-default.yml +2 -0
- package/test/configFiles/device/labjack-u3-default.yml +2 -0
- package/test/configFiles/device/modbus-partial.yml +4 -0
- package/test/configFiles/device/modbus-std-extended.yaml +23 -0
- package/test/configFiles/device/modbus-std.yaml +34 -0
- package/test/configFiles/device/modbus-tcp-default.yml +3 -0
- package/test/configFiles/device/mqtt-default.yml +2 -0
- package/test/configFiles/device/mqtt-lincoln-default.yml +3 -0
- package/test/configFiles/device/mqtt-lincoln-full.yml +7 -0
- package/test/configFiles/device/mtconnect-adapter-default.yml +3 -0
- package/test/configFiles/device/mtconnect-adapter-keys.yaml +18 -0
- package/test/configFiles/device/mtconnect-complex-keys.yaml +17 -0
- package/test/configFiles/device/mtconnect-default.yml +3 -0
- package/test/configFiles/device/mtconnect-duplicate-allow-keys.yaml +10 -0
- package/test/configFiles/device/mtconnect-duplicate-declare-keys.yaml +8 -0
- package/test/configFiles/device/mtconnect-duplicate-deny-keys.yaml +10 -0
- package/test/configFiles/device/mtconnect-haas-default.yml +3 -0
- package/test/configFiles/device/mtconnect-std.yaml +18 -0
- package/test/configFiles/device/null-default.yml +1 -0
- package/test/configFiles/device/opcua-bad-tag.yml +18 -0
- package/test/configFiles/device/opcua-bad-tag2.yml +18 -0
- package/test/configFiles/device/opcua-default.yml +3 -0
- package/test/configFiles/device/opcua-std.yml +18 -0
- package/test/configFiles/dump-test.yml +11 -0
- package/test/configFiles/expressionCond.yml +46 -0
- package/test/configFiles/min-config-t4.yaml +4 -0
- package/test/configFiles/min-config-u3.yaml +3 -0
- package/test/configFiles/missing-device.yaml +2 -0
- package/test/configFiles/parse-error1.yml +9 -0
- package/test/configFiles/parse-error2.yml +9 -0
- package/test/configFiles/repro/buffer-convert-repro.yml +15 -0
- package/test/configFiles/repro/chained-delay-timing-repro.yml +13 -0
- package/test/configFiles/repro/count-init-repro.yml +45 -0
- package/test/configFiles/repro/cycle-break-repro.yml +44 -0
- package/test/configFiles/repro/debounce-repro.yml +46 -0
- package/test/configFiles/repro/diff-count-repro.yml +34 -0
- package/test/configFiles/repro/engine-hang-repro.yml +9 -0
- package/test/configFiles/repro/latch-apm-repro.yml +26 -0
- package/test/configFiles/repro/lockout-count-repro.yml +33 -0
- package/test/configFiles/repro/program-extract-repro.yml +38 -0
- package/test/configFiles/repro/state-latch-repro.yml +47 -0
- package/test/configFiles/repro/ternary-repro.yml +26 -0
- package/test/configFiles/transform/debounce.yml +12 -0
- package/test/configFiles/transform/expression.yml +34 -0
- package/test/configFiles/transform/ignoreValue.yml +31 -0
- package/test/configFiles/transform/latch.yml +11 -0
- package/test/configFiles/transform/latchValue.yml +31 -0
- package/test/configFiles/transform/logicAnd.yml +14 -0
- package/test/configFiles/transform/logicOr.yml +14 -0
- package/test/configFiles/transform/map.yml +19 -0
- package/test/configFiles/transform/maxLength.yml +13 -0
- package/test/configFiles/transform/offDelay.yml +12 -0
- package/test/configFiles/transform/pattern-escape.yml +10 -0
- package/test/configFiles/transform/pattern-match.yml +57 -0
- package/test/configFiles/transform/pattern-replace.yml +34 -0
- package/test/configFiles/transform/pattern-test.yml +25 -0
- package/test/configFiles/transform/reject.yml +24 -0
- package/test/configFiles/transform/risingEdgeCounter.yml +36 -0
- package/test/configFiles/transform/source.yml +20 -0
- package/test/configFiles/transform/state.yml +56 -0
- package/test/configFiles/transform/toggle.yml +19 -0
- package/test/configFiles/transform/whenUnavailable.yml +19 -0
- package/test/dataFiles/noisy-pulse.txt +11330 -0
- package/test/dataItems.test.js +140 -0
- package/test/engine-v1-tests.js +418 -0
- package/test/engine-v2-tests.js +284 -0
- package/test/expression-tests.js +171 -0
- package/test/expressionService.test.js +154 -0
- package/test/expressionServiceCondition.test.js +130 -0
- package/test/repro/buffer-convert-repro.test.js +38 -0
- package/test/repro/chained-delay-timing-repro.test.js +34 -0
- package/test/repro/count-init-repro.test.js +46 -0
- package/test/repro/cylce-break-repro.test.js +57 -0
- package/test/repro/debounce-repro.test.js +65 -0
- package/test/repro/diff-count-repro.test.js +79 -0
- package/test/repro/engine-hang-repro.test.js +38 -0
- package/test/repro/latch-apm-repro.test.js +119 -0
- package/test/repro/lockout-count-repro.test.js +84 -0
- package/test/repro/program-extract-repro.test.js +40 -0
- package/test/repro/state-latch-repro.test.js +63 -0
- package/test/repro/ternary-repro.test.js +43 -0
- package/test/transform/accumulte.test.js +18 -0
- package/test/transform/average.test.js +22 -0
- package/test/transform/debounce.test.js +70 -0
- package/test/transform/downsample.test.js +30 -0
- package/test/transform/edge.test.js +27 -0
- package/test/transform/expression.test.js +189 -0
- package/test/transform/fallingEdge.test.js +59 -0
- package/test/transform/fromBuffer.test.js +60 -0
- package/test/transform/hash.test.js +34 -0
- package/test/transform/ignoreValue.test.js +123 -0
- package/test/transform/invert.test.js +26 -0
- package/test/transform/latch.test.js +33 -0
- package/test/transform/latchValue.test.js +126 -0
- package/test/transform/logicAnd.test.js +80 -0
- package/test/transform/logicOr.test.js +80 -0
- package/test/transform/map.test.js +42 -0
- package/test/transform/max.test.js +30 -0
- package/test/transform/maxLength.test.js +32 -0
- package/test/transform/min.test.js +30 -0
- package/test/transform/minDelta.test.js +14 -0
- package/test/transform/offDelay.test.js +123 -0
- package/test/transform/onDelay.test.js +105 -0
- package/test/transform/patternEscape.test.js +18 -0
- package/test/transform/patternMatch.test.js +177 -0
- package/test/transform/patternReplace.test.js +95 -0
- package/test/transform/patternTest.test.js +105 -0
- package/test/transform/rateOfChange.test.js +34 -0
- package/test/transform/reject.test.js +56 -0
- package/test/transform/resample.test.js +193 -0
- package/test/transform/risingEdge.test.js +60 -0
- package/test/transform/risingEdgeCounter.test.js +227 -0
- package/test/transform/sampleInterval.test.js +22 -0
- package/test/transform/source.test.js +137 -0
- package/test/transform/state.test.js +248 -0
- package/test/transform/threshold.test.js +78 -0
- package/test/transform/toBuffer.test.js +60 -0
- package/test/transform/toggle.test.js +92 -0
- package/test/transform/trim.test.js +30 -0
- package/test/transform/valueChange.test.js +14 -0
- package/test/transform/valueDecrease.test.js +32 -0
- package/test/transform/valueIncrease.test.js +32 -0
- package/test/transform/valueIncreaseDiff.test.js +32 -0
- package/test/transform/whenUnavailable.test.js +93 -0
- package/test/transform/windowCount.test.js +26 -0
- package/test/util/testUtils.js +405 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Increments a counter for every rising edge (F->T) in a digital stream.
|
|
8
|
+
*
|
|
9
|
+
* For every incoming sample, the internal counter value will be emitted. When a rising edge is found,
|
|
10
|
+
* the counter will be incremented before the output value is emitted.
|
|
11
|
+
*/
|
|
12
|
+
class RisingEdgeCounterFilter extends TransformState {
|
|
13
|
+
constructor({ engine, path, args: { mergeWindow, accum = {}, reset = {} } }) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.mergeWindow = mergeWindow || 0;
|
|
17
|
+
this.engine = engine;
|
|
18
|
+
this.accumExpression = accum.compiledExpression;
|
|
19
|
+
this.accumSources = engine.expressionService.expressionTriggers(`${path}.accum`);
|
|
20
|
+
this.resetExpression = reset.compiledExpression;
|
|
21
|
+
this.resetSources = engine.expressionService.expressionTriggers(`${path}.reset`);
|
|
22
|
+
|
|
23
|
+
this.defaultValue = false;
|
|
24
|
+
this.initReset();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static op = 'count';
|
|
28
|
+
|
|
29
|
+
static create(args) {
|
|
30
|
+
return new RisingEdgeCounterFilter(args);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static parseConfig(configUtil, defn) {
|
|
34
|
+
configUtil.requireObjectParams(defn.args, 'count', {
|
|
35
|
+
optional: true,
|
|
36
|
+
availableAttributes: ['amount', 'reset', 'merge-window'],
|
|
37
|
+
defaultAttribute: 'amount',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let accum = _.get(defn.args, 'amount', defn.args.count);
|
|
41
|
+
if (!_.isUndefined(accum)) {
|
|
42
|
+
try {
|
|
43
|
+
const expression = accum.toString();
|
|
44
|
+
const compiledExpression = configUtil.compileExpression(expression, this.op);
|
|
45
|
+
accum = { expression, compiledExpression };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
configUtil.throwConfigError(`Problem evaluating expression: ${err.message}`, this.op);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let reset = _.get(defn.args, 'reset');
|
|
52
|
+
if (!_.isUndefined(reset)) {
|
|
53
|
+
try {
|
|
54
|
+
const expression = reset.toString();
|
|
55
|
+
const compiledExpression = configUtil.compileExpression(expression, this.op);
|
|
56
|
+
reset = { expression, compiledExpression };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
configUtil.throwConfigError(`Problem evaluating expression: ${err.message}`, this.op);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mergeWindow = _.get(defn.args, 'merge-window');
|
|
63
|
+
if (!_.isUndefined(mergeWindow)) {
|
|
64
|
+
configUtil.requirePositive(mergeWindow, 'merge-window', this.op);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
defn.args = { accum, reset, mergeWindow };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static getExpressions(configUtil, body, info) {
|
|
71
|
+
return [
|
|
72
|
+
...configUtil.getExpressions(body, { ...info, field: 'accum' }, true),
|
|
73
|
+
...configUtil.getExpressions(body, { ...info, field: 'reset' }),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
initReset() {
|
|
78
|
+
this.value = false;
|
|
79
|
+
this.previousValue = false;
|
|
80
|
+
this.count = 0;
|
|
81
|
+
this.preCount = 0;
|
|
82
|
+
this.pendingChangeTime = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
supportsPendingChanges() {
|
|
86
|
+
return !!this.mergeWindow;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
update(context, value, time) {
|
|
90
|
+
this.lastSuppliedValue = value;
|
|
91
|
+
this.lastSampleTime = time;
|
|
92
|
+
if (!this.available) {
|
|
93
|
+
this.available = true;
|
|
94
|
+
|
|
95
|
+
// Don't count when becoming available already in a high state
|
|
96
|
+
this.emitUpdate(context);
|
|
97
|
+
this.filter(context, value, time);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.filter(context, value, time);
|
|
102
|
+
this.emitUpdate(context);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
emitUpdate(context) {
|
|
106
|
+
if (this.mergeWindow && this.lastSampleTime >= this.pendingChangeTime) {
|
|
107
|
+
this.count += this.preCount;
|
|
108
|
+
this.preCount = 0;
|
|
109
|
+
this.pendingChangeTime = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (this.value && this.valueTime === this.lastSampleTime) {
|
|
113
|
+
if (this.accumExpression) {
|
|
114
|
+
const localState = { this: this.count };
|
|
115
|
+
const unavailTerm = this.accumSources.reduce((res, name) => {
|
|
116
|
+
const state = this.engine.getState(name);
|
|
117
|
+
localState[name] = state?.value;
|
|
118
|
+
return res || !state || !state.available;
|
|
119
|
+
}, false);
|
|
120
|
+
|
|
121
|
+
if (!unavailTerm) {
|
|
122
|
+
try {
|
|
123
|
+
this.preCount += this.accumExpression.evaluate(localState);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
// Probably a type error in one of the arguments.
|
|
126
|
+
this.recordError(context, err, this.valueTime);
|
|
127
|
+
this.setUnavailable(context, this.valueTime);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
this.preCount += 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.mergeWindow) {
|
|
136
|
+
this.pendingChangeTime = this.lastSampleTime + this.mergeWindow;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!this.mergeWindow) {
|
|
141
|
+
this.count += this.preCount;
|
|
142
|
+
this.preCount = 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// While resetExpression evals true, force count to 0
|
|
146
|
+
if (this.resetExpression) {
|
|
147
|
+
const localState = { this: this.count };
|
|
148
|
+
const unavailTerm = this.resetSources.reduce((res, name) => {
|
|
149
|
+
const state = this.engine.getState(name);
|
|
150
|
+
localState[name] = state?.value;
|
|
151
|
+
return res || !state || !state.available;
|
|
152
|
+
}, false);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
if (!unavailTerm && this.resetExpression.evaluate(localState)) {
|
|
156
|
+
this.count = 0;
|
|
157
|
+
this.preCount = 0;
|
|
158
|
+
this.pendingChangeTime = null;
|
|
159
|
+
this.emit('update', this.count, this.lastSampleTime, context);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// Probably a type error in one of the arguments.
|
|
164
|
+
this.recordError(context, err, this.lastSampleTime);
|
|
165
|
+
this.setUnavailable(context, this.lastSampleTime);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.emit('update', this.count, this.lastSampleTime, context);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setUnavailable(context, time) {
|
|
174
|
+
super.setUnavailable(context, time);
|
|
175
|
+
this.initReset();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = RisingEdgeCounterFilter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DownsampleFilter = require('./downsample');
|
|
4
|
+
|
|
5
|
+
// TODO: Might go away with better general handling of downsample/resample.
|
|
6
|
+
class SampleIntervalFilter extends DownsampleFilter {
|
|
7
|
+
constructor(interval) {
|
|
8
|
+
super(interval * 500);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = SampleIntervalFilter;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
const ChainSource = require('./util/chainSource');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transforms each value by a pre-parsed mathjs expression or re-emits a specified source.
|
|
9
|
+
*
|
|
10
|
+
* Filter must be provided with the backing engine to have access to current variable states, which can
|
|
11
|
+
* be referenced in expressions by name. Unlike the expression transform, 'this' is not a valid
|
|
12
|
+
* identifier when an expression is supplied.
|
|
13
|
+
*
|
|
14
|
+
* Source discards the incoming value, so should only be used at the start of a transform chain.
|
|
15
|
+
*
|
|
16
|
+
* MathJS expressions are capable of dealing with any input types, and may produce output of differing type.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class SourceFilter extends TransformState {
|
|
20
|
+
constructor({ engine, path, rootPath, baseTransform, args: { expression, compiledExpression } }) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
if (baseTransform) {
|
|
24
|
+
this.origin = new ChainSource(this, { engine, path, rootPath }, {
|
|
25
|
+
onWatchSource: (name) => {
|
|
26
|
+
this.constantSource = false;
|
|
27
|
+
if (expression === name) {
|
|
28
|
+
this.simpleSource = true;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.engine = engine;
|
|
35
|
+
this.expression = expression;
|
|
36
|
+
this.compiledExpression = compiledExpression;
|
|
37
|
+
this.simpleSource = !compiledExpression;
|
|
38
|
+
this.selfSources = engine.expressionService.expressionTriggers(path);
|
|
39
|
+
|
|
40
|
+
// If there's no variables in the expression, it's a constant that can be evaluated once
|
|
41
|
+
if (_.isEmpty(this.selfSources)) {
|
|
42
|
+
try {
|
|
43
|
+
this.lastSuppliedValue = this.compiledExpression.evaluate();
|
|
44
|
+
this.constantSource = true;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// Oh well
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static op = 'source';
|
|
52
|
+
|
|
53
|
+
static create(args) {
|
|
54
|
+
return new SourceFilter(args);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static parseConfig(configUtil, defn) {
|
|
58
|
+
let expression = defn.args[defn.func];
|
|
59
|
+
if (_.isUndefined(expression)) {
|
|
60
|
+
expression = '';
|
|
61
|
+
}
|
|
62
|
+
expression = expression.toString();
|
|
63
|
+
|
|
64
|
+
if (_.isEmpty(expression)) {
|
|
65
|
+
configUtil.throwConfigError('source must be an identifier or expression', this.op);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const compiledExpression = configUtil.compileExpression(expression, this.op, {});
|
|
70
|
+
defn.args = { expression, compiledExpression };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
configUtil.throwConfigError(`Problem evaluating source expression: ${err.message}`, this.op);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static getExpressions(configUtil, body, info) {
|
|
77
|
+
return configUtil.getExpressions(body, info);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
update(context, value, time) {
|
|
81
|
+
if (value === 'UNAVAILABLE') {
|
|
82
|
+
this.setUnavailable(context, time);
|
|
83
|
+
} else {
|
|
84
|
+
super.update(context, value, time);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
filter(context, value, time) {
|
|
89
|
+
if (this.constantSource || this.simpleSource) {
|
|
90
|
+
this.commitValue(context, value, time);
|
|
91
|
+
} else {
|
|
92
|
+
const localState = {};
|
|
93
|
+
const unavailTerm = _.reduce(this.selfSources, (res, name) => {
|
|
94
|
+
const state = this.engine.getState(name);
|
|
95
|
+
localState[name] = state?.value;
|
|
96
|
+
return res || !state || !state.available;
|
|
97
|
+
}, false);
|
|
98
|
+
|
|
99
|
+
if (unavailTerm) {
|
|
100
|
+
this.setUnavailable(context, time);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
this.commitValue(context, this.compiledExpression.evaluate(localState), time);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Huh the engine doesn't recognize some value probably
|
|
108
|
+
// This is not entirely unexpected early as the transform may be populated with names later
|
|
109
|
+
this.recordError(context, err, time);
|
|
110
|
+
this.setUnavailable(context, time);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = SourceFilter;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
const ChainSource = require('./util/chainSource');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A reverse-switch style if/else chain
|
|
9
|
+
*
|
|
10
|
+
* For every incoming sample, each conditional assignment rule (a 'case') will be evaluated in order until
|
|
11
|
+
* one of them returns true. The value associated with the expression will be emitted. If no cases match,
|
|
12
|
+
* the incoming sample will be emitted instead.
|
|
13
|
+
*
|
|
14
|
+
* If any conditional assignment expressions include a variable that updates, the entire set of rules will be
|
|
15
|
+
* re-evaluated at that timestamp.
|
|
16
|
+
*
|
|
17
|
+
* Config Example 1:
|
|
18
|
+
* - source: exec-in
|
|
19
|
+
* - state:
|
|
20
|
+
* - ACTIVE: this and not in-fault
|
|
21
|
+
* - STOPPED: this and in-fault
|
|
22
|
+
* - READY: true
|
|
23
|
+
*
|
|
24
|
+
* Config Example 2:
|
|
25
|
+
* - source: in-fault
|
|
26
|
+
* - state:
|
|
27
|
+
* - select: Normal
|
|
28
|
+
* when: this
|
|
29
|
+
* - select: Machine is currently in fault
|
|
30
|
+
* when: not this
|
|
31
|
+
*/
|
|
32
|
+
class StateFilter extends TransformState {
|
|
33
|
+
constructor({ engine, path, rootPath, baseTransform, args: { rules = {} } }) {
|
|
34
|
+
super();
|
|
35
|
+
|
|
36
|
+
if (baseTransform) {
|
|
37
|
+
const pathSet = _.map(rules, (rule, index) => `${path}.${index}.when`);
|
|
38
|
+
this.origin = new ChainSource(this, {
|
|
39
|
+
engine,
|
|
40
|
+
path: pathSet,
|
|
41
|
+
rootPath,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.engine = engine;
|
|
46
|
+
this.rules = _.map(rules, (rule, index) => {
|
|
47
|
+
return {
|
|
48
|
+
value: rule.value,
|
|
49
|
+
expression: rule.expression,
|
|
50
|
+
compiledExpression: rule.compiledExpression,
|
|
51
|
+
expressionSources: engine.expressionService.expressionTriggers(`${path}.${index}.when`),
|
|
52
|
+
hasThis: engine.expressionService.findName(rule.expression, 'this'),
|
|
53
|
+
sourceCache: {},
|
|
54
|
+
resultCache: undefined,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static op = 'state';
|
|
60
|
+
|
|
61
|
+
static create(args) {
|
|
62
|
+
return new StateFilter(args);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static parseConfig(configUtil, defn) {
|
|
66
|
+
const items = defn.args;
|
|
67
|
+
if (!_.isArray(items)) {
|
|
68
|
+
configUtil.throwConfigError('Expected list of conditional assignments', this.op);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rules = _.map(items, (item) => {
|
|
72
|
+
if (!_.isObject(item)) {
|
|
73
|
+
configUtil.throwConfigError('Invalid conditional assignment', this.op);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// convert shorthand to longhand
|
|
77
|
+
if (_.size(item) === 1) {
|
|
78
|
+
const assignValue = _.first(_.keys(item));
|
|
79
|
+
const assignExpr = item[assignValue];
|
|
80
|
+
item = {
|
|
81
|
+
select: assignValue,
|
|
82
|
+
when: assignExpr,
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
if (!_.has(item, 'select')) {
|
|
86
|
+
configUtil.throwConfigError('Expected "select" argument in non-shorthand conditional assignment', this.op);
|
|
87
|
+
}
|
|
88
|
+
if (!_.has(item, 'when')) {
|
|
89
|
+
configUtil.throwConfigError('Expected "when" argument in non-shorthand conditional assignment', this.op);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const expression = item.when.toString();
|
|
95
|
+
const compiledExpression = configUtil.compileExpression(expression, this.op);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
value: item.select,
|
|
99
|
+
expression,
|
|
100
|
+
compiledExpression,
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
configUtil.throwConfigError(`Problem evaluating expression for case "${item.select}": ${err.message}`, this.op);
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
defn.args = { rules };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static getExpressions(configUtil, body, info) {
|
|
112
|
+
const { varName, index, attribute } = info;
|
|
113
|
+
|
|
114
|
+
if (!_.isArray(body)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return _.map(body, (rule, ruleIndex) => {
|
|
119
|
+
if (_.isObject(rule)) {
|
|
120
|
+
let expr;
|
|
121
|
+
if (_.size(rule) === 1) {
|
|
122
|
+
expr = _.first(_.values(rule));
|
|
123
|
+
} else if (!_.isUndefined(rule.when)) {
|
|
124
|
+
expr = rule.when;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (expr) {
|
|
128
|
+
expr = expr.toString();
|
|
129
|
+
return [{
|
|
130
|
+
path: `variables.${varName}.${index}.${attribute}.${ruleIndex}.when`,
|
|
131
|
+
expression: expr,
|
|
132
|
+
}, {
|
|
133
|
+
path: `variables.${varName}`,
|
|
134
|
+
expression: expr,
|
|
135
|
+
}];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
filter(context, value, time) {
|
|
143
|
+
if (!_.reduce(this.rules, (pass, rule) => {
|
|
144
|
+
if (pass) {
|
|
145
|
+
return pass;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let cacheInvalid = false;
|
|
149
|
+
if (rule.hasThis) {
|
|
150
|
+
if (this.origin) {
|
|
151
|
+
// Any 'this' term is automatically unavailable if we're first in transform chain
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
cacheInvalid = rule.sourceCache.this !== value;
|
|
156
|
+
rule.sourceCache.this = value;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const unavailTerm = _.reduce(rule.expressionSources, (res, name) => {
|
|
160
|
+
const state = this.engine.getState(name);
|
|
161
|
+
if (state) {
|
|
162
|
+
cacheInvalid = cacheInvalid || (rule.sourceCache[name] !== state.value);
|
|
163
|
+
rule.sourceCache[name] = state.value;
|
|
164
|
+
}
|
|
165
|
+
return res || !state || !state.available;
|
|
166
|
+
}, false);
|
|
167
|
+
|
|
168
|
+
if (cacheInvalid) {
|
|
169
|
+
rule.resultCache = undefined;
|
|
170
|
+
}
|
|
171
|
+
if (unavailTerm) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
if (rule.resultCache === undefined) {
|
|
177
|
+
rule.resultCache = rule.compiledExpression.evaluate(rule.sourceCache);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (rule.resultCache) {
|
|
181
|
+
this.commitValue(context, rule.value, time);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// Probably a type error in one of the arguments.
|
|
186
|
+
this.recordError(context, err, time);
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}, false)) {
|
|
190
|
+
// If we get here, none of the rules matched, so pass-through the input
|
|
191
|
+
if (this.origin) {
|
|
192
|
+
// ... unless we're first in the chain
|
|
193
|
+
this.setUnavailable(context, time);
|
|
194
|
+
} else {
|
|
195
|
+
this.commitValue(context, value, time);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = StateFilter;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts an analog stream to a digital stream by applying a threshold to each incoming value.
|
|
8
|
+
*
|
|
9
|
+
* For each sample equal or above the threshold, true will be emitted, and for each sample below
|
|
10
|
+
* the threshold, false will be emitted.
|
|
11
|
+
*/
|
|
12
|
+
class ThresholdFilter extends TransformState {
|
|
13
|
+
constructor({ args: { threshold, direction } }) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.threshold = threshold;
|
|
17
|
+
this.direction = direction ?? 'above';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static op = 'threshold';
|
|
21
|
+
|
|
22
|
+
static create(args) {
|
|
23
|
+
return new ThresholdFilter(args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static parseConfig(configUtil, defn) {
|
|
27
|
+
const { value: threshold, usingShorthand } = configUtil.getArgOrShorthand(defn, 'value');
|
|
28
|
+
configUtil.requireNumeric(threshold, usingShorthand ? this.op : 'value', this.op);
|
|
29
|
+
|
|
30
|
+
const direction = defn.args.direction ?? 'above';
|
|
31
|
+
configUtil.requireValueIn(direction, ['above', 'below'], 'direction', this.op);
|
|
32
|
+
defn.args = { threshold, direction };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
filter(context, value, time) {
|
|
36
|
+
if (value === true || value === 'true') {
|
|
37
|
+
this.commitValue(context, true, time);
|
|
38
|
+
} else if (value === false || value === 'false') {
|
|
39
|
+
this.commitValue(context, false, time);
|
|
40
|
+
} else {
|
|
41
|
+
value = _.toNumber(value);
|
|
42
|
+
if (!_.isNaN(value)) {
|
|
43
|
+
const cmp = this.direction === 'above' ? value >= this.threshold : value <= this.threshold;
|
|
44
|
+
this.commitValue(context, cmp, time);
|
|
45
|
+
} else {
|
|
46
|
+
this.commitValue(context, false, time);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = ThresholdFilter;
|