@machinemetrics/io-adapter-lib 2.32.0 → 2.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.32.2]
4
+ - Add support to reclassify conditions to NORMAL
5
+
6
+ ## [2.32.1]
7
+ - Added opcua-conditions block to opcua config
8
+ - Fixed message overrides in conditions
9
+
3
10
  ## [2.32.0]
4
11
  - Repackaged for public NPM publishing on @machinemetrics/io-adapter-lib
5
12
 
@@ -350,6 +350,9 @@ class AdapterConfig {
350
350
  if (item['reclassify-fault']) {
351
351
  source.reclassifyFault = allowDenyList.parseWildcardKeyList(item['reclassify-fault'], `conditions.${key}.reclassify-fault-codes`);
352
352
  }
353
+ if (item['reclassify-normal']) {
354
+ source.reclassifyNormal = allowDenyList.parseWildcardKeyList(item['reclassify-normal'], `conditions.${key}.reclassify-normal-codes`);
355
+ }
353
356
 
354
357
  if (item.overrides) {
355
358
  if (!_.isArray(item.overrides)) {
@@ -379,7 +382,7 @@ class AdapterConfig {
379
382
  defn.message = override['override-message'];
380
383
  defn.messagePath = `conditions.${key}.overrides.${index}.override-message`;
381
384
  defn.messageIsStatic = this.expressionService.isStaticExpression(defn.messagePath);
382
- defn.messageResolver = this.expressionResolver(defn.code, defn.messagePath);
385
+ defn.messageResolver = this.expressionResolver(defn.message, defn.messagePath);
383
386
  defn.messageResolverTriggers = this.expressionService.expressionTriggers(defn.messagePath);
384
387
  this.conditionsByExpression[defn.messagePath] = key;
385
388
  }
@@ -19,13 +19,11 @@ class OpcuaConfig {
19
19
  }
20
20
 
21
21
  getIdentifiers(config = {}) {
22
- if (_.isEmpty(config.tags)) {
23
- // No tags, this should be a softer warning
24
- return [];
25
- }
22
+ const keyset = Object.keys(config.tags || {});
26
23
 
27
- const keyset = Object.keys(config.tags);
28
- if (config['opcua-condition-key']) {
24
+ if (config['opcua-conditions']?.key) {
25
+ keyset.push(config['opcua-conditions'].key);
26
+ } else if (config['opcua-condition-key']) {
29
27
  keyset.push(config['opcua-condition-key']);
30
28
  }
31
29
 
@@ -55,7 +53,17 @@ class OpcuaConfig {
55
53
  this.userPrivateKeyPath = config['user-private-key-path'];
56
54
 
57
55
  this.tags = this.loadTags(config.tags);
56
+
57
+ // opcua-condition-key deprecated in favor of opcua-conditions
58
58
  this.conditionKey = config['opcua-condition-key'];
59
+ this.opcuaConditions = this.loadConditions(config['opcua-conditions']);
60
+
61
+ // For backwards compatibility
62
+ if (this.conditionKey && !this.opcuaConditions?.key) {
63
+ this.opcuaConditions = { key: this.conditionKey };
64
+ } else if (this.opcuaConditions?.key) {
65
+ this.conditionKey = this.opcuaConditions.key;
66
+ }
59
67
 
60
68
  this.securityMode = this.checkSecurityMode(config['security-mode']);
61
69
  this.securityPolicy = this.checkSecurityPolicy(config['security-policy']);
@@ -106,6 +114,45 @@ class OpcuaConfig {
106
114
  }).keyBy('name').value();
107
115
  }
108
116
 
117
+ loadConditions(conditions) {
118
+ if (!conditions) {
119
+ return {};
120
+ }
121
+
122
+ try {
123
+ if (!conditions.key) {
124
+ throw new ConfigError('key is required').atAttribute('key');
125
+ }
126
+
127
+ const result = {
128
+ key: conditions.key,
129
+ };
130
+
131
+ if (_.has(conditions, 'fault-threshold')) {
132
+ const faultThreshold = conditions['fault-threshold'];
133
+ if (!_.isNumber(faultThreshold)) {
134
+ throw new ConfigError('fault-threshold must be a number').atAttribute('fault-threshold');
135
+ }
136
+ result.faultThreshold = faultThreshold;
137
+ }
138
+
139
+ if (_.has(conditions, 'warning-threshold')) {
140
+ const warningThreshold = conditions['warning-threshold'];
141
+ if (!_.isNumber(warningThreshold)) {
142
+ throw new ConfigError('warning-threshold must be a number').atAttribute('warning-threshold');
143
+ }
144
+ result.warningThreshold = warningThreshold;
145
+ }
146
+
147
+ return result;
148
+ } catch (error) {
149
+ if (error instanceof ConfigError) {
150
+ error.atSection('opcua-conditions');
151
+ }
152
+ throw error;
153
+ }
154
+ }
155
+
109
156
  checkTagPath(defn) {
110
157
  const path = defn.path;
111
158
  if (!path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machinemetrics/io-adapter-lib",
3
- "version": "2.32.0",
3
+ "version": "2.32.2",
4
4
  "description": "Configuration and engine implementation for MachineMetrics AdapterScripts and adapters",
5
5
  "main": "index.js",
6
6
  "license": "UNLICENSED",
@@ -4,7 +4,8 @@
4
4
  },
5
5
  "rules": {
6
6
  "import/no-extraneous-dependencies": [ "off" ],
7
- "prefer-arrow-callback": "off"
7
+ "prefer-arrow-callback": "off",
8
+ "no-unused-expressions": "off"
8
9
  },
9
10
  "env": {
10
11
  "mocha": true
@@ -72,6 +72,37 @@ describe('data items', async function () {
72
72
  validateCondition(captured[10], 'cond1', 'FAULT', 5000, 'A2', 'Code A2');
73
73
  validateCondition(captured[11], 'cond2', 'FAULT', 6000, 'B33', 'Code B33');
74
74
  });
75
+
76
+ it('loads reclassification config', async function () {
77
+ expect(config.adapter.passthroughConditions).to.exist;
78
+ expect(config.adapter.passthroughConditions.system).to.exist;
79
+ expect(config.adapter.passthroughConditions.system.length).to.eq(1);
80
+
81
+ const source = config.adapter.passthroughConditions.system[0];
82
+ expect(source.name).to.eq('cond4');
83
+ expect(source.reclassifyFault).to.exist;
84
+ expect(source.reclassifyWarning).to.exist;
85
+ expect(source.reclassifyNormal).to.exist;
86
+ });
87
+
88
+ it('reclassifies codes based on patterns', async function () {
89
+ const source = config.adapter.passthroughConditions.system[0];
90
+
91
+ // Test FAULT reclassification
92
+ expect(source.reclassifyFault[0].test('HIGH_TEMP')).to.eq(true);
93
+ expect(source.reclassifyFault[0].test('HIGH_PRESSURE')).to.eq(true);
94
+ expect(source.reclassifyFault[0].test('MEDIUM_TEMP')).to.eq(false);
95
+
96
+ // Test WARNING reclassification
97
+ expect(source.reclassifyWarning[0].test('MEDIUM_TEMP')).to.eq(true);
98
+ expect(source.reclassifyWarning[0].test('MEDIUM_PRESSURE')).to.eq(true);
99
+ expect(source.reclassifyWarning[0].test('LOW_TEMP')).to.eq(false);
100
+
101
+ // Test NORMAL reclassification
102
+ expect(source.reclassifyNormal[0].test('LOW_TEMP')).to.eq(true);
103
+ expect(source.reclassifyNormal[0].test('LOW_PRESSURE')).to.eq(true);
104
+ expect(source.reclassifyNormal[0].test('HIGH_TEMP')).to.eq(false);
105
+ });
75
106
  });
76
107
 
77
108
  function validateCondition(entry, key, value, time, code, message) {
@@ -7,9 +7,12 @@ const testUtils = require('../util/testUtils');
7
7
  describe('OPC-UA config tests', function () {
8
8
  let config;
9
9
  let defaultConfig;
10
+ let conditionsConfig;
11
+
10
12
  before(async () => {
11
13
  defaultConfig = await testUtils.loadConfig('device/opcua-default.yml');
12
14
  config = await testUtils.loadConfig('device/opcua-std.yml');
15
+ conditionsConfig = await testUtils.loadConfig('device/opcua-conditions.yml');
13
16
  });
14
17
 
15
18
  it('loads minimal config with defaults', async function () {
@@ -24,6 +27,7 @@ describe('OPC-UA config tests', function () {
24
27
  expect(defaultConfig.device.userCertificatePath).to.eq(undefined);
25
28
  expect(defaultConfig.device.userPrivateKeyPath).to.eq(undefined);
26
29
  expect(defaultConfig.device.conditionKey).to.eq(undefined);
30
+ expect(defaultConfig.device.opcuaConditions).to.deep.eq({});
27
31
  });
28
32
 
29
33
  it('reads tags', async function () {
@@ -31,7 +35,7 @@ describe('OPC-UA config tests', function () {
31
35
  });
32
36
 
33
37
  it('loaded expression service', async function () {
34
- const deviceNames = ['fan-speed', 'pump-speed', 'some-date', 'pressure'];
38
+ const deviceNames = ['fan-speed', 'pump-speed', 'some-date', 'pressure', 'system'];
35
39
  const allNames = [...deviceNames, 'device-connected'];
36
40
  expect(config.expressionService.definedNames()).to.deep.eq(allNames);
37
41
  expect(config.expressionService.definedNames('device')).to.deep.eq(deviceNames);
@@ -40,6 +44,11 @@ describe('OPC-UA config tests', function () {
40
44
  expect(config.expressionService.referencedNames('device')).to.deep.eq(['fan-speed', 'pump-speed']);
41
45
  });
42
46
 
47
+ it('migrates deprecated opcua-condition-key to opcua-conditions', async function () {
48
+ expect(config.device.conditionKey).to.eq('system');
49
+ expect(config.device.opcuaConditions).to.deep.eq({ key: 'system' });
50
+ });
51
+
43
52
  it('catches bad tag definition (bad path syntax)', async function () {
44
53
  try {
45
54
  await testUtils.loadConfig('device/opcua-bad-tag.yml');
@@ -94,4 +103,13 @@ describe('OPC-UA config tests', function () {
94
103
  testPath('10853');
95
104
  testPath('nsu=http://test.org/UA/Data/;i=10853');
96
105
  });
106
+
107
+ it('loads opcua-conditions with all properties', async function () {
108
+ expect(conditionsConfig.device.opcuaConditions).to.deep.eq({
109
+ key: 'system',
110
+ faultThreshold: 100,
111
+ warningThreshold: 50,
112
+ });
113
+ expect(conditionsConfig.device.conditionKey).to.eq('system');
114
+ });
97
115
  });
@@ -5,6 +5,8 @@ declare-keys:
5
5
  - key2
6
6
  - key3
7
7
  - key4
8
+ - system:
9
+ type: condition
8
10
  variables:
9
11
  keymod:
10
12
  - source: key2
@@ -35,3 +37,11 @@ conditions:
35
37
  override-message: ${this} coolant=${key1}
36
38
  - override-code: Y
37
39
  override-message: Z
40
+ cond4:
41
+ source: system
42
+ reclassify-fault:
43
+ - HIGH*
44
+ reclassify-warning:
45
+ - MEDIUM*
46
+ reclassify-normal:
47
+ - LOW*
@@ -0,0 +1,7 @@
1
+ version: 2
2
+ device: opc-ua
3
+ endpoint: opc.tcp://localhost:4840
4
+ opcua-conditions:
5
+ key: system
6
+ fault-threshold: 100
7
+ warning-threshold: 50
@@ -1,6 +1,7 @@
1
1
  version: 2
2
2
  device: opc-ua
3
3
  endpoint: opc.tcp://opcuademo.sterfive.com:26543
4
+ opcua-condition-key: system
4
5
  mtconnect-port: 8001
5
6
  tags:
6
7
  fan-speed: