@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,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
const RingBuffer = require('./util/ringBuffer');
|
|
6
|
+
|
|
7
|
+
// TODO: Consider changing count implementation to support non-clocked input
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* For each incoming sample, outputs the max of the last N samples seen.
|
|
11
|
+
*/
|
|
12
|
+
class MaxFilter extends TransformState {
|
|
13
|
+
constructor(sampleCount) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.sampleCount = sampleCount;
|
|
17
|
+
this.initReset();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static op = 'max';
|
|
21
|
+
|
|
22
|
+
static create(args) {
|
|
23
|
+
return new MaxFilter(args.args.count);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static parseConfig(configUtil, defn) {
|
|
27
|
+
const count = _.get(defn.args, 'count');
|
|
28
|
+
configUtil.requirePositive(count, this.op, this.op);
|
|
29
|
+
|
|
30
|
+
defn.args = { count };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
initReset() {
|
|
34
|
+
this.buffer = new RingBuffer(this.sampleCount);
|
|
35
|
+
this.max = 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
filter(context, value, time) {
|
|
39
|
+
const oldest = this.buffer.get(this.buffer.getTailIndex());
|
|
40
|
+
this.buffer.push(value);
|
|
41
|
+
|
|
42
|
+
if (this.max === oldest) {
|
|
43
|
+
this.max = _.max(this.buffer.values());
|
|
44
|
+
} else if (this.max < value) {
|
|
45
|
+
this.max = value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.commitValue(context, this.max, time);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setUnavailable(context, time) {
|
|
52
|
+
super.setUnavailable(context, time);
|
|
53
|
+
this.initReset();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = MaxFilter;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncates string value to maximum specified length
|
|
8
|
+
*/
|
|
9
|
+
class MaxLengthFilter extends TransformState {
|
|
10
|
+
constructor(count) {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
this.maxLength = count;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static op = 'max-length';
|
|
17
|
+
|
|
18
|
+
static create(args) {
|
|
19
|
+
return new MaxLengthFilter(args.args.count);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static parseConfig(configUtil, defn) {
|
|
23
|
+
const count = _.get(defn.args, 'count', defn.args[defn.func]);
|
|
24
|
+
configUtil.requirePositive(count, this.op, this.op);
|
|
25
|
+
|
|
26
|
+
defn.args = { count };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
filter(context, value, time) {
|
|
30
|
+
if (_.isString(value)) {
|
|
31
|
+
value = value.substring(0, this.maxLength);
|
|
32
|
+
} else if (_.isArray(value)) {
|
|
33
|
+
value = value.slice(0, this.maxLength);
|
|
34
|
+
}
|
|
35
|
+
this.commitValue(context, value, time);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = MaxLengthFilter;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
const RingBuffer = require('./util/ringBuffer');
|
|
6
|
+
|
|
7
|
+
// TODO: Consider changing count implementation to support non-clocked input
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* For each incoming sample, outputs the min of the last N samples seen.
|
|
11
|
+
*/
|
|
12
|
+
class MinFilter extends TransformState {
|
|
13
|
+
constructor(sampleCount) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.sampleCount = sampleCount;
|
|
17
|
+
this.initReset();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static op = 'min';
|
|
21
|
+
|
|
22
|
+
static create(args) {
|
|
23
|
+
return new MinFilter(args.args.count);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static parseConfig(configUtil, defn) {
|
|
27
|
+
const count = _.get(defn.args, 'count');
|
|
28
|
+
configUtil.requirePositive(count, this.op, this.op);
|
|
29
|
+
|
|
30
|
+
defn.args = { count };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
initReset() {
|
|
34
|
+
this.buffer = new RingBuffer(this.sampleCount);
|
|
35
|
+
this.min = 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
filter(context, value, time) {
|
|
39
|
+
const oldest = this.buffer.get(this.buffer.getTailIndex());
|
|
40
|
+
this.buffer.push(value);
|
|
41
|
+
|
|
42
|
+
if (this.min === oldest) {
|
|
43
|
+
this.min = _.min(this.buffer.values());
|
|
44
|
+
} else if (this.min > value) {
|
|
45
|
+
this.min = value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.commitValue(context, this.min, time);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setUnavailable(context, time) {
|
|
52
|
+
super.setUnavailable(context, time);
|
|
53
|
+
this.initReset();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = MinFilter;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compares each incoming sample with the last change that was emitted. If the difference between the two is larger
|
|
8
|
+
* than the specified delta, the new sample is recorded and emitted, otherwise the old sample is emitted.
|
|
9
|
+
*
|
|
10
|
+
* This is similar to quantization, but instead of fixed output values, any newly recorded output value is guaranteed
|
|
11
|
+
* to be at least delta greater or lesser than the previous recorded output value.
|
|
12
|
+
*/
|
|
13
|
+
class MinDelta extends TransformState {
|
|
14
|
+
constructor(delta) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this.delta = delta;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static op = 'min-delta';
|
|
21
|
+
|
|
22
|
+
static create(args) {
|
|
23
|
+
return new MinDelta(args.args.delta);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static parseConfig(configUtil, defn) {
|
|
27
|
+
const delta = _.get(defn.args, 'delta', defn.args[defn.func]);
|
|
28
|
+
configUtil.requirePositive(delta, this.op, this.op);
|
|
29
|
+
|
|
30
|
+
defn.args = { delta };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
filter(context, value, time) {
|
|
34
|
+
if (Math.abs(value - this.value) >= this.delta) {
|
|
35
|
+
this.commitValue(context, value, time);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = MinDelta;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TransformState = require('./transformState');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Holds a digital signal in the 'on' state for a given window of time when the incoming signal goes low.
|
|
7
|
+
*
|
|
8
|
+
* The internal counter of the off delay resets on each falling edge, so a signal that switches state
|
|
9
|
+
* frequently may be emitted in the on state continuously.
|
|
10
|
+
*
|
|
11
|
+
* This transform introduces lag into the signal equal to the window size.
|
|
12
|
+
*/
|
|
13
|
+
class OffDelayFilter extends TransformState {
|
|
14
|
+
constructor(window) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this.window = window;
|
|
18
|
+
this.defaultValue = false;
|
|
19
|
+
this.initReset();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static op = 'off-delay';
|
|
23
|
+
|
|
24
|
+
static create(args) {
|
|
25
|
+
return new OffDelayFilter(args.args.window);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static parseConfig(configUtil, defn) {
|
|
29
|
+
const window = defn.args.window ?? defn.args[defn.func];
|
|
30
|
+
configUtil.requirePositive(window, this.op, this.op);
|
|
31
|
+
|
|
32
|
+
defn.args = { window };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initReset() {
|
|
36
|
+
this.value = false;
|
|
37
|
+
this.lowStartTime = null;
|
|
38
|
+
this.pendingChangeTime = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
supportsPendingChanges() {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
filter(context, value, time) {
|
|
46
|
+
// Whenever the real signal transitions to low, record a marker at that time.
|
|
47
|
+
if (!value && this.lowStartTime === null) {
|
|
48
|
+
this.lowStartTime = time;
|
|
49
|
+
this.pendingChangeTime = time + this.window;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lowStart = this.lowStartTime || 0;
|
|
53
|
+
const insideOffDelayWindow = lowStart + this.window > time;
|
|
54
|
+
if (insideOffDelayWindow && !value) {
|
|
55
|
+
// When incoming signal is low but has not been held low long enough, skip
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (value || (!value && time >= this.pendingChangeTime)) {
|
|
60
|
+
this.pendingChangeTime = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!insideOffDelayWindow && value && this.value && this.lowStartTime !== null) {
|
|
64
|
+
// If we're outside of the delay window AND both the incoming signal and held signal are high,
|
|
65
|
+
// AND we recorded a lowStartTime marker, then there is high->low transition between this sample
|
|
66
|
+
// and the previous sample that must be recognized.
|
|
67
|
+
|
|
68
|
+
const offTime = lowStart + this.window;
|
|
69
|
+
if (offTime === time) {
|
|
70
|
+
this.commitValue(context, false, offTime);
|
|
71
|
+
this.emitCommittedUpdate(context);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// When the real signal transitions to high, clear the low signal marker.
|
|
76
|
+
if (value && this.lowStartTime !== null) {
|
|
77
|
+
this.lowStartTime = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.commitValue(context, value, time);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setUnavailable(context, time) {
|
|
84
|
+
super.setUnavailable(context, time);
|
|
85
|
+
this.initReset();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = OffDelayFilter;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TransformState = require('./transformState');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Holds a digital signal in the 'off' state for a given window of time when the incoming signal goes high.
|
|
7
|
+
*
|
|
8
|
+
* The internal counter of the on delay resets on each rising edge, so a signal that switches state
|
|
9
|
+
* frequently may be emitted in the off state continuously.
|
|
10
|
+
*
|
|
11
|
+
* This transform introduces lag into the signal equal to the window size.
|
|
12
|
+
*/
|
|
13
|
+
class OnDelayFilter extends TransformState {
|
|
14
|
+
constructor(window) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this.window = window;
|
|
18
|
+
this.defaultValue = false;
|
|
19
|
+
this.initReset();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static op = 'on-delay';
|
|
23
|
+
|
|
24
|
+
static create(args) {
|
|
25
|
+
return new OnDelayFilter(args.args.window);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static parseConfig(configUtil, defn) {
|
|
29
|
+
const window = defn.args.window ?? defn.args[defn.func];
|
|
30
|
+
configUtil.requirePositive(window, this.op, this.op);
|
|
31
|
+
|
|
32
|
+
defn.args = { window };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initReset() {
|
|
36
|
+
this.value = false;
|
|
37
|
+
this.highStartTime = null;
|
|
38
|
+
this.pendingChangeTime = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
supportsPendingChanges() {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
filter(context, value, time) {
|
|
46
|
+
// Whenever the real signal transitions to high, record a marker at that time.
|
|
47
|
+
if (value && this.highStartTime === null) {
|
|
48
|
+
this.highStartTime = time;
|
|
49
|
+
this.pendingChangeTime = time + this.window;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const highStart = this.highStartTime || 0;
|
|
53
|
+
const insideOnDelayWindow = highStart + this.window > time;
|
|
54
|
+
if (insideOnDelayWindow && value) {
|
|
55
|
+
// When incoming signal is high but has not been held high long enough, skip
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!value || (value && time >= this.pendingChangeTime)) {
|
|
60
|
+
this.pendingChangeTime = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!insideOnDelayWindow && !value && !this.value && this.highStartTime !== null) {
|
|
64
|
+
// If we're outside of the delay window AND both the incoming signal and held signal are low,
|
|
65
|
+
// AND we recorded a highStartTime marker, then there is low->high transition between this sample
|
|
66
|
+
// and the previous sample that must be recognized.
|
|
67
|
+
|
|
68
|
+
const onTime = highStart + this.window;
|
|
69
|
+
if (onTime === time) {
|
|
70
|
+
this.commitValue(context, true, onTime);
|
|
71
|
+
this.emitCommittedUpdate(context);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// When the real signal transitions to low, clear the high signal marker.
|
|
76
|
+
if (!value && this.highStartTime !== null) {
|
|
77
|
+
this.highStartTime = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.commitValue(context, value, time);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setUnavailable(context, time) {
|
|
84
|
+
super.setUnavailable(context, time);
|
|
85
|
+
this.initReset();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = OnDelayFilter;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Escapes a string to be usable as a literal in a regex pattern.
|
|
8
|
+
*/
|
|
9
|
+
class PatternEscapeFilter extends TransformState {
|
|
10
|
+
static op = 'pattern-escape';
|
|
11
|
+
|
|
12
|
+
static create(_args) {
|
|
13
|
+
return new PatternEscapeFilter();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
filter(context, value, time) {
|
|
17
|
+
if (_.isString(value)) {
|
|
18
|
+
value = _.escapeRegExp(value);
|
|
19
|
+
}
|
|
20
|
+
this.commitValue(context, value, time);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = PatternEscapeFilter;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const TransformState = require('./transformState');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Attempts to match a pattern in a source string and capture the match as output.
|
|
8
|
+
*/
|
|
9
|
+
class PatternMatchFilter extends TransformState {
|
|
10
|
+
constructor({ engine, path, args: { pattern, flags, group, noMatch, matchIndex } }) {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
this.patternPath = `${path}.pattern`;
|
|
14
|
+
this.elsePath = `${path}.else`;
|
|
15
|
+
|
|
16
|
+
this.engine = engine;
|
|
17
|
+
this.staticPattern = engine.expressionService.isStaticExpression(this.patternPath);
|
|
18
|
+
this.patternSources = engine.expressionService.expressionTriggers(this.patternPath);
|
|
19
|
+
this.staticElse = engine.expressionService.isStaticExpression(this.elsePath);
|
|
20
|
+
this.elseSources = engine.expressionService.expressionTriggers(this.elsePath);
|
|
21
|
+
|
|
22
|
+
this.flags = flags || '';
|
|
23
|
+
this.hasMatchIndex = _.isNumber(matchIndex);
|
|
24
|
+
this.hasGroup = _.isNumber(group);
|
|
25
|
+
|
|
26
|
+
const globalFlag = this.flags.indexOf('g') >= 0;
|
|
27
|
+
if (this.hasMatchIndex && !globalFlag) {
|
|
28
|
+
this.flags = `${flags}g`;
|
|
29
|
+
} else if (!this.hasMatchIndex && globalFlag) {
|
|
30
|
+
this.hasMatchIndex = true;
|
|
31
|
+
this.matchIndex = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.staticPattern) {
|
|
35
|
+
this.pattern = new RegExp(pattern, this.flags);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.group = group || 0;
|
|
39
|
+
this.noMatch = noMatch;
|
|
40
|
+
this.matchIndex = matchIndex || 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static op = 'pattern-match';
|
|
44
|
+
|
|
45
|
+
static create(args) {
|
|
46
|
+
return new PatternMatchFilter(args);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static parseConfig(configUtil, defn) {
|
|
50
|
+
configUtil.requireObjectParams(defn.args, this.op, {
|
|
51
|
+
optional: false,
|
|
52
|
+
availableAttributes: ['pattern', 'group', 'index', 'else'],
|
|
53
|
+
defaultAttribute: 'pattern',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const pattern = _.get(defn.args, 'pattern', defn.args[defn.func]);
|
|
57
|
+
if (_.isEmpty(pattern)) {
|
|
58
|
+
configUtil.throwConfigError('pattern-match must have an pattern', this.op);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const decomp = configUtil.decomposePattern(pattern, 'imusg');
|
|
62
|
+
configUtil.testPattern(decomp.pattern, decomp.flags, this.op);
|
|
63
|
+
|
|
64
|
+
const group = _.get(defn.args, 'group');
|
|
65
|
+
if (!_.isNil(group)) {
|
|
66
|
+
configUtil.requireZeroPositive(group, 'group', this.op);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const matchIndex = _.get(defn.args, 'index');
|
|
70
|
+
if (!_.isNil(matchIndex)) {
|
|
71
|
+
configUtil.requireZeroPositive(matchIndex, 'index', this.op);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const noMatch = _.get(defn.args, 'else');
|
|
75
|
+
|
|
76
|
+
defn.args = { ...decomp, group, noMatch, matchIndex };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static getStringExpressions(configUtil, body, info) {
|
|
80
|
+
if (_.isString(body)) {
|
|
81
|
+
body = { pattern: body };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const patternVal = configUtil.decomposePattern(body?.pattern?.toString(), 'imusg').pattern;
|
|
85
|
+
const elseVal = body?.else?.toString();
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
...configUtil.getStringExpressions(body, patternVal, { ...info, field: 'pattern' }),
|
|
89
|
+
...configUtil.getStringExpressions(body, elseVal, { ...info, field: 'else' }),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
filter(context, value, time) {
|
|
94
|
+
value = value?.toString() ?? '';
|
|
95
|
+
let pattern = this.pattern;
|
|
96
|
+
|
|
97
|
+
if (!this.staticPattern) {
|
|
98
|
+
const localState = { this: value };
|
|
99
|
+
const unavailTerm = _.reduce(this.patternSources, (res, name) => {
|
|
100
|
+
const state = this.engine.getState(name);
|
|
101
|
+
localState[name] = state?.value;
|
|
102
|
+
return res || !state || !state.available;
|
|
103
|
+
}, false);
|
|
104
|
+
|
|
105
|
+
if (unavailTerm) {
|
|
106
|
+
this.setUnavailable(context, time);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
pattern = this.engine.expressionService.computeStringExpression(this.patternPath, localState);
|
|
112
|
+
pattern = new RegExp(pattern, this.flags);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
this.recordError(context, err, time);
|
|
115
|
+
this.setUnavailable(context, time);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let match = null;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (this.hasGroup && this.hasMatchIndex) {
|
|
124
|
+
match = this.matchGroupIndex(pattern, value);
|
|
125
|
+
} else if (this.hasGroup) {
|
|
126
|
+
match = this.matchSimple(pattern, value, this.group);
|
|
127
|
+
} else if (this.hasMatchIndex) {
|
|
128
|
+
match = this.matchSimple(pattern, value, this.matchIndex);
|
|
129
|
+
} else {
|
|
130
|
+
match = this.matchSimple(pattern, value, 0);
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this.recordError(context, err, time);
|
|
134
|
+
this.setUnavailable(context, time);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (match === null) {
|
|
139
|
+
this.setUnavailable(context, time);
|
|
140
|
+
} else {
|
|
141
|
+
this.commitValue(context, match, time);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
matchSimple(pattern, value, index) {
|
|
146
|
+
const match = value.match(pattern);
|
|
147
|
+
if (!match || match[index] === undefined) {
|
|
148
|
+
return this.makeNoMatch(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return match[index];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
matchGroupIndex(pattern, value) {
|
|
155
|
+
const matches = [...value.matchAll(pattern)];
|
|
156
|
+
if (!matches) {
|
|
157
|
+
return this.makeNoMatch(value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.matchIndex >= matches.length) {
|
|
161
|
+
return this.makeNoMatch(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const match = matches[this.matchIndex];
|
|
165
|
+
if (!match || match[this.group] === undefined) {
|
|
166
|
+
return this.makeNoMatch(value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return match[this.group];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
makeNoMatch(value) {
|
|
173
|
+
if (_.isUndefined(this.noMatch)) {
|
|
174
|
+
return '';
|
|
175
|
+
} else if (this.staticElse) {
|
|
176
|
+
return this.noMatch;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const localState = { this: value };
|
|
180
|
+
const unavailTerm = _.reduce(this.elseSources, (res, name) => {
|
|
181
|
+
const state = this.engine.getState(name);
|
|
182
|
+
localState[name] = state?.value;
|
|
183
|
+
return res || !state || !state.available;
|
|
184
|
+
}, false);
|
|
185
|
+
|
|
186
|
+
if (unavailTerm) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return this.engine.expressionService.computeStringExpression(this.elsePath, localState);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = PatternMatchFilter;
|