@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,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const ConfigError = require('../configError');
|
|
5
|
+
|
|
6
|
+
const dacPins = _.times(2, i => `DAC${i}`);
|
|
7
|
+
const analogPins = [
|
|
8
|
+
..._.times(4, i => `AIN${i}`),
|
|
9
|
+
..._.times(4, i => `pin-${i}`), // legacy alias
|
|
10
|
+
];
|
|
11
|
+
const flexiblePins = [
|
|
12
|
+
..._.times(4, i => `FIO${i + 4}`),
|
|
13
|
+
..._.times(4, i => `EIO${i}`),
|
|
14
|
+
..._.times(8, i => `pin-${i + 4}`), // legacy alias
|
|
15
|
+
];
|
|
16
|
+
const digitalPins = [
|
|
17
|
+
..._.times(4, i => `EIO${i + 4}`),
|
|
18
|
+
..._.times(4, i => `CIO${i}`),
|
|
19
|
+
];
|
|
20
|
+
const allPins = [
|
|
21
|
+
...dacPins,
|
|
22
|
+
...analogPins,
|
|
23
|
+
...flexiblePins,
|
|
24
|
+
...digitalPins,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const pins = {
|
|
28
|
+
dacPins,
|
|
29
|
+
analogPins,
|
|
30
|
+
flexiblePins,
|
|
31
|
+
digitalPins,
|
|
32
|
+
allPins,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Legacy is used by V2 pin-X naming and all V1 configs
|
|
36
|
+
const legacyPinMap = _.mapValues(_.keyBy(_.range(12), i => i), i => `pin-${i}`);
|
|
37
|
+
|
|
38
|
+
const makeAnalogPin = ({ name }) => {
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
mode: 'analog',
|
|
42
|
+
direction: 'input',
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const makeDigitalPin = ({ name, direction = 'input', value }) => {
|
|
47
|
+
return {
|
|
48
|
+
name,
|
|
49
|
+
mode: 'digital',
|
|
50
|
+
direction,
|
|
51
|
+
value,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const makeDacPin = ({ name, value = 0 }) => {
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
mode: 'dac',
|
|
59
|
+
value,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const checkPinMode = (defn, name) => {
|
|
64
|
+
if (!_.includes(flexiblePins, name)) {
|
|
65
|
+
if (!_.isUndefined(defn.mode)) {
|
|
66
|
+
throw new ConfigError('Mode can only be set on flexible pins').atAttribute('mode');
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const mode = defn.mode || 'analog';
|
|
71
|
+
if (!_.includes(['digital', 'analog'], mode)) {
|
|
72
|
+
throw new ConfigError('Mode must be one of [analog, digital]').atAttribute('mode');
|
|
73
|
+
}
|
|
74
|
+
return mode;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const checkPinDirection = (defn, name) => {
|
|
78
|
+
const isDigital = _.includes(digitalPins, name) || (_.includes(flexiblePins, name) && defn.mode === 'digital');
|
|
79
|
+
if (!isDigital) {
|
|
80
|
+
if (!_.isUndefined(defn.direction)) {
|
|
81
|
+
throw new ConfigError('Direction can only be set on digital pins').atAttribute('direction');
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const direction = defn.direction || 'input';
|
|
86
|
+
if (!_.includes(['input', 'output'], direction)) {
|
|
87
|
+
throw new ConfigError('Direction must be one of [input, output]').atAttribute('direction');
|
|
88
|
+
}
|
|
89
|
+
return direction;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const checkPinDacValue = (defn, name) => {
|
|
93
|
+
if (!_.includes(dacPins, name)) {
|
|
94
|
+
if (!_.isUndefined(defn.value)) {
|
|
95
|
+
throw new ConfigError('Output value can only be set on DAC pins').atAttribute('value');
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const value = defn.value || 0;
|
|
100
|
+
if (!_.isNumber(value) || value < 0 || value > 5) {
|
|
101
|
+
throw new ConfigError('Output value must be between 0 and 5 (volts)').atAttribute('value');
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const checkPinOutValue = (defn, name) => {
|
|
107
|
+
const isDigital = _.includes(digitalPins, name) || (_.includes(flexiblePins, name) && defn.mode === 'digital');
|
|
108
|
+
if (!isDigital) {
|
|
109
|
+
if (!_.isUndefined(defn.value)) {
|
|
110
|
+
throw new ConfigError('Value can only be set on digital pins').atAttribute('value');
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const value = defn.value;
|
|
115
|
+
if (!_.isUndefined(value) && defn.direction !== 'output') {
|
|
116
|
+
throw new ConfigError('Value can only be set on an output pin').atAttribute('value');
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const deviceIdentifiers = () => {
|
|
122
|
+
return _.map([...analogPins, ...flexiblePins, ...digitalPins], (name) => {
|
|
123
|
+
return {
|
|
124
|
+
name,
|
|
125
|
+
channel: 'device',
|
|
126
|
+
defaultValue: 0,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const scanInterval = (config) => {
|
|
132
|
+
const scanIntervalRequest = config['scan-interval'] || 0.002;
|
|
133
|
+
const scanInterval4MHz = Math.floor(scanIntervalRequest * 4000000);
|
|
134
|
+
if (scanInterval4MHz <= 0) {
|
|
135
|
+
throw new ConfigError('scan-interval must be > 0').atAttribute('scan-interval');
|
|
136
|
+
}
|
|
137
|
+
if (scanInterval4MHz > 65535) {
|
|
138
|
+
throw new ConfigError('scan-interval must be <= 0.016').atAttribute('scan-interval');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return scanInterval4MHz / 4000000;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const loadUnreferencedPins = (data) => {
|
|
145
|
+
// Fill out any pins not explicitly defined with default configs
|
|
146
|
+
_.each(analogPins, (name) => {
|
|
147
|
+
if (!data[name]) {
|
|
148
|
+
data[name] = makeAnalogPin({ name });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
_.each(flexiblePins, (name) => {
|
|
152
|
+
if (!data[name]) {
|
|
153
|
+
data[name] = makeAnalogPin({ name });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
_.each(digitalPins, (name) => {
|
|
157
|
+
if (!data[name]) {
|
|
158
|
+
data[name] = makeDigitalPin({ name });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
_.each(dacPins, (name) => {
|
|
162
|
+
if (!data[name]) {
|
|
163
|
+
data[name] = makeDacPin({ name });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return data;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const loadPins = (deviceName, config = {}) => {
|
|
171
|
+
const data = _(config.pins).map((defn, name) => {
|
|
172
|
+
if (!defn) {
|
|
173
|
+
defn = { };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Deal with legacy pin definitions
|
|
177
|
+
if (legacyPinMap[+name]) {
|
|
178
|
+
name = legacyPinMap[+name];
|
|
179
|
+
if (_.isString(defn)) {
|
|
180
|
+
if (_.includes(analogPins, name) && defn === 'analog') {
|
|
181
|
+
defn = { };
|
|
182
|
+
} else {
|
|
183
|
+
defn = { mode: defn };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!_.includes(allPins, name)) {
|
|
189
|
+
throw new ConfigError(`${name} is not a valid ${deviceName} pin`).atPath(`pins.${name}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const mode = checkPinMode(defn, name);
|
|
194
|
+
const direction = checkPinDirection(defn, name);
|
|
195
|
+
const outValue = checkPinOutValue(defn, name);
|
|
196
|
+
|
|
197
|
+
if (_.includes(analogPins, name)) {
|
|
198
|
+
return makeAnalogPin({ name });
|
|
199
|
+
} else if (_.includes(flexiblePins, name)) {
|
|
200
|
+
if (mode === 'analog') {
|
|
201
|
+
return makeAnalogPin({ name });
|
|
202
|
+
}
|
|
203
|
+
return makeDigitalPin({ name, direction, value: outValue });
|
|
204
|
+
} else if (_.includes(digitalPins, name)) {
|
|
205
|
+
return makeDigitalPin({ name, direction, value: outValue });
|
|
206
|
+
} else if (_.includes(dacPins, name)) {
|
|
207
|
+
const dacValue = checkPinDacValue(defn, name);
|
|
208
|
+
return makeDacPin({ name, value: dacValue });
|
|
209
|
+
}
|
|
210
|
+
throw new ConfigError(`${name} is an unknown pin type`).atPath(`pins.${name}`);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (err instanceof ConfigError) {
|
|
213
|
+
err.atSection(`pins.${name}`);
|
|
214
|
+
}
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
}).keyBy('name').value();
|
|
218
|
+
|
|
219
|
+
return loadUnreferencedPins(data);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
pins,
|
|
224
|
+
deviceIdentifiers,
|
|
225
|
+
scanInterval,
|
|
226
|
+
loadPins,
|
|
227
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const ConfigError = require('../configError');
|
|
5
|
+
|
|
6
|
+
const checkRequired = (defn, fieldName, required = false) => {
|
|
7
|
+
if (required && !_.has(defn, fieldName)) {
|
|
8
|
+
throw new ConfigError(`${fieldName} is required`).atAttribute(fieldName);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
checkInterval: (defn, fieldName, defaultValue = 1) => {
|
|
14
|
+
const interval = defn[fieldName] ?? defaultValue;
|
|
15
|
+
const pattern = /^\d+\s*(ms|s|m|h|d|w)?$/;
|
|
16
|
+
if (!_.isString(interval) || !pattern.test(interval)) {
|
|
17
|
+
throw new ConfigError(`${fieldName} must be > 0 with optional time unit (ms, s, m, h, d, w)`).atAttribute(fieldName);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return interval;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
checkTimezone: (defn, fieldName) => {
|
|
24
|
+
const timezone = defn[fieldName];
|
|
25
|
+
if (timezone) {
|
|
26
|
+
if (!_.isString(timezone) || !timezone.match(/^[A-Za-z0-9+-_]+(\/[A-Za-z0-9+-_]+)*$/)) {
|
|
27
|
+
throw new ConfigError(`${fieldName} must be a valid TZ identifier`).atAttribute(fieldName);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return timezone;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
checkScanInterval: (defn, fieldName, { defaultValue = 1, scale = 1 } = {}) => {
|
|
35
|
+
const scanRate = defn[fieldName] ?? defaultValue;
|
|
36
|
+
if (scanRate < 0) {
|
|
37
|
+
throw new ConfigError('Scan interval must be > 0').atAttribute(fieldName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return scanRate * scale;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
checkEndpoint: (defn, fieldName, { required = true, defaultPort = undefined } = {}) => {
|
|
44
|
+
checkRequired(defn, fieldName, required);
|
|
45
|
+
const endpoint = defn[fieldName];
|
|
46
|
+
|
|
47
|
+
if (endpoint) {
|
|
48
|
+
if (!endpoint.match(/^[\w.]+(:\d+)?$/)) {
|
|
49
|
+
throw new ConfigError('Invalid endpoint').atAttribute(fieldName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [host, port] = endpoint.split(':');
|
|
53
|
+
return { host, port: port ? (+port) : defaultPort };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
checkNumber: (defn, fieldName, {
|
|
60
|
+
required = false,
|
|
61
|
+
defaultValue = 0,
|
|
62
|
+
min = undefined,
|
|
63
|
+
max = undefined,
|
|
64
|
+
exclusiveMin = undefined,
|
|
65
|
+
exclusiveMax = undefined,
|
|
66
|
+
} = {}) => {
|
|
67
|
+
checkRequired(defn, fieldName, required);
|
|
68
|
+
|
|
69
|
+
const value = defn[fieldName] ?? defaultValue;
|
|
70
|
+
|
|
71
|
+
if (!_.isNil(min) && value < min) {
|
|
72
|
+
throw new ConfigError(`${fieldName} must be >= ${min}`).atAttribute(fieldName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!_.isNil(max) && value > max) {
|
|
76
|
+
throw new ConfigError(`${fieldName} must be <= ${max}`).atAttribute(fieldName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!_.isNil(exclusiveMin) && value <= exclusiveMin) {
|
|
80
|
+
throw new ConfigError(`${fieldName} must be > ${exclusiveMin}`).atAttribute(fieldName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!_.isNil(exclusiveMax) && value >= exclusiveMax) {
|
|
84
|
+
throw new ConfigError(`${fieldName} must be < ${exclusiveMax}`).atAttribute(fieldName);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
checkBoolean: (defn, fieldName, { required = false, defaultValue = false } = {}) => {
|
|
91
|
+
checkRequired(defn, fieldName, required);
|
|
92
|
+
|
|
93
|
+
let value = defn[fieldName] ?? defaultValue;
|
|
94
|
+
|
|
95
|
+
if (_.isString(value)) {
|
|
96
|
+
value = value.toLowerCase();
|
|
97
|
+
} else if (_.isNumber(value)) {
|
|
98
|
+
value = value.toString();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (value === true || value === 'true' || value === 'yes' || value === '1') {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (value === false || value === 'false' || value === 'no' || value === '0') {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new ConfigError('Value must be true or false').atAttribute(fieldName);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
checkReset: (defn, fieldName = 'reset') => {
|
|
112
|
+
const adapterReset = defn[fieldName];
|
|
113
|
+
|
|
114
|
+
if (!adapterReset) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!_.isObject(adapterReset)) {
|
|
119
|
+
throw new ConfigError(`${fieldName} must be an object`).atAttribute(fieldName);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const result = {};
|
|
123
|
+
|
|
124
|
+
if (_.has(adapterReset, 'interval')) {
|
|
125
|
+
const interval = adapterReset.interval;
|
|
126
|
+
if (!_.isNumber(interval) || interval <= 0) {
|
|
127
|
+
throw new ConfigError('interval must be a number > 0').atPath(`${fieldName}.interval`);
|
|
128
|
+
}
|
|
129
|
+
result.interval = interval;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (_.has(adapterReset, 'no-change-timeout')) {
|
|
133
|
+
const noChangeTimeout = adapterReset['no-change-timeout'];
|
|
134
|
+
if (!_.isNumber(noChangeTimeout) || noChangeTimeout <= 0) {
|
|
135
|
+
throw new ConfigError('no-change-timeout must be a number > 0').atPath(`${fieldName}.no-change-timeout`);
|
|
136
|
+
}
|
|
137
|
+
result.noChangeTimeout = noChangeTimeout;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return _.isEmpty(result) ? null : result;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
class ConfigError extends Error {
|
|
6
|
+
atPath(path) {
|
|
7
|
+
if (_.isString(path)) {
|
|
8
|
+
path = path.split('.');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.section = _.initial(path);
|
|
12
|
+
this.attribute = _.last(path);
|
|
13
|
+
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
atSection(section) {
|
|
18
|
+
if (_.isString(section)) {
|
|
19
|
+
section = section.split('.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.section = section;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
atAttribute(attribute) {
|
|
27
|
+
this.attribute = attribute;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = ConfigError;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const ConfigError = require('../configError');
|
|
5
|
+
|
|
6
|
+
const digiCountPins = [
|
|
7
|
+
..._.times(8, i => `DI${i}`),
|
|
8
|
+
..._.times(8, i => `pin-${i}`),
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const allPins = [
|
|
12
|
+
...digiCountPins,
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// Legacy is used by V2 pin-X naming and all V1 configs
|
|
16
|
+
const legacyPinMap = _.mapValues(_.keyBy(_.range(8), i => i), i => `pin-${i}`);
|
|
17
|
+
|
|
18
|
+
class AdamConfig {
|
|
19
|
+
constructor(expressionService) {
|
|
20
|
+
this.expressionService = expressionService;
|
|
21
|
+
|
|
22
|
+
_.each(this.getIdentifiers(), name => this.expressionService.addName(name));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getIdentifiers() {
|
|
26
|
+
return _.map(allPins, (name) => {
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
channel: 'device',
|
|
30
|
+
defaultValue: 0,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
parse(config = {}) {
|
|
36
|
+
if (!config.host) {
|
|
37
|
+
throw new ConfigError('No host defined');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.host = config.host;
|
|
41
|
+
this.pins = this.loadPins(config);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
loadPins(config = {}) {
|
|
45
|
+
return _(config.pins).map((defn, name) => {
|
|
46
|
+
if (!defn) {
|
|
47
|
+
defn = { };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Deal with legacy pin definitions
|
|
51
|
+
if (legacyPinMap[+name]) {
|
|
52
|
+
name = legacyPinMap[+name];
|
|
53
|
+
}
|
|
54
|
+
if (_.isString(defn)) {
|
|
55
|
+
defn = { mode: defn };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!_.includes(allPins, name)) {
|
|
59
|
+
throw new ConfigError(`${name} is not a valid ADAM-6052 pin`).atPath(`pins.${name}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const mode = this.checkPinMode(defn, name);
|
|
64
|
+
|
|
65
|
+
if (mode === 'digital') {
|
|
66
|
+
return this.makeDigitalPin({ name });
|
|
67
|
+
} else if (mode === 'counter') {
|
|
68
|
+
return this.makeCounterPin({ name });
|
|
69
|
+
}
|
|
70
|
+
throw new ConfigError(`${name} is an unknown pin type`).atPath(`pins.${name}`);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof ConfigError) {
|
|
73
|
+
err.atSection(`pins.${name}`);
|
|
74
|
+
}
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}).keyBy('name').value();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
makeDigitalPin({ name }) {
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
mode: 'digital',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
makeCounterPin({ name }) {
|
|
88
|
+
return {
|
|
89
|
+
name,
|
|
90
|
+
mode: 'counter',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
checkPinMode(defn) {
|
|
95
|
+
const mode = defn.mode || 'digital';
|
|
96
|
+
if (!_.includes(['digital', 'counter'], mode)) {
|
|
97
|
+
throw new ConfigError('Mode must be one of [digital, counter]').atAttribute('mode');
|
|
98
|
+
}
|
|
99
|
+
return mode;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = AdamConfig;
|