@machinemetrics/io-adapter-lib 2.34.0 → 2.35.1

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.35.1]
4
+ - Fixed value-increase and value-decrease to not emit true when coming back from UNAVAILABLE
5
+ - Behavior change: value-decrease does not emit true on start
6
+
7
+ ## [2.35.0]
8
+ - Added data-events config support
9
+
3
10
  ## [2.34.0]
4
11
  - Added support for initializing variables from constant source or expression ops
5
12
  - Fixed map not recognizing external identifiers in sub-ops
@@ -100,7 +100,20 @@ class AdapterConfig {
100
100
  });
101
101
  }).flattenDeep().compact().value();
102
102
 
103
- return [...dataItems, ...conditions];
103
+ const dataEvents = _(config['data-events']).map((event, key) => {
104
+ const exprs = [];
105
+ if (_.isString(event.payload)) {
106
+ exprs.push({ path: `data-events.${key}.payload`, expression: event.payload });
107
+ }
108
+ _.each(event.triggers, (trigger, i) => {
109
+ if (_.isString(trigger)) {
110
+ exprs.push({ path: `data-events.${key}.triggers.${i}`, expression: trigger });
111
+ }
112
+ });
113
+ return exprs;
114
+ }).flattenDeep().compact().value();
115
+
116
+ return [...dataItems, ...conditions, ...dataEvents];
104
117
  }
105
118
 
106
119
  getStringExpressions(config = {}) {
@@ -150,6 +163,7 @@ class AdapterConfig {
150
163
  parse(config = {}) {
151
164
  this.loadDataItems(config['data-items']);
152
165
  this.loadConditions(config.conditions);
166
+ this.loadDataEvents(config['data-events']);
153
167
 
154
168
  this.passthroughConditions = _(this.conditions).filter((cond) => {
155
169
  return cond.passthroughSource;
@@ -524,6 +538,62 @@ class AdapterConfig {
524
538
  };
525
539
  }
526
540
 
541
+ loadDataEvents(events) {
542
+ this.dataEventsByExpression = {};
543
+ this.dataEvents = _.mapValues(events || {}, (event, key) => {
544
+ if (!_.isString(event.payload)) {
545
+ const configErr = new ConfigError(`Data event '${key}' has no payload`);
546
+ throw configErr.atSection(`data-events.${key}`).atAttribute('payload');
547
+ }
548
+
549
+ let payloadDefn;
550
+ try {
551
+ const payloadPath = `data-events.${key}.payload`;
552
+ const compiledPayload = this.expressionService.compileExpression(event.payload);
553
+ this.dataEventsByExpression[payloadPath] = key;
554
+ payloadDefn = {
555
+ rawExpression: event.payload,
556
+ expression: compiledPayload,
557
+ };
558
+ } catch (err) {
559
+ const configErr = new ConfigError(`Problem evaluating payload expression: ${err.message}`);
560
+ throw configErr.atSection(`data-events.${key}`).atAttribute('payload');
561
+ }
562
+
563
+ if (!_.isArray(event.triggers) || _.isEmpty(event.triggers)) {
564
+ const configErr = new ConfigError(`Data event '${key}' has no triggers`);
565
+ throw configErr.atSection(`data-events.${key}`).atAttribute('triggers');
566
+ }
567
+
568
+ const triggers = _.map(event.triggers, (trigger, i) => {
569
+ if (!_.isString(trigger)) {
570
+ const configErr = new ConfigError(`Data event '${key}' trigger at index ${i} must be a string expression`);
571
+ throw configErr.atSection(`data-events.${key}.triggers`);
572
+ }
573
+
574
+ try {
575
+ const triggerPath = `data-events.${key}.triggers.${i}`;
576
+ const compiledTrigger = this.expressionService.compileExpression(trigger);
577
+ this.dataEventsByExpression[triggerPath] = key;
578
+ return {
579
+ rawExpression: trigger,
580
+ expression: compiledTrigger,
581
+ triggerVariables: this.expressionService.expressionTriggers(triggerPath),
582
+ };
583
+ } catch (err) {
584
+ const configErr = new ConfigError(`Problem evaluating trigger expression: ${err.message}`);
585
+ throw configErr.atSection(`data-events.${key}.triggers`).atAttribute(i);
586
+ }
587
+ });
588
+
589
+ return {
590
+ name: key,
591
+ payload: payloadDefn,
592
+ triggers,
593
+ };
594
+ });
595
+ }
596
+
527
597
  triggeredDataItems(name) {
528
598
  const triggeredPaths = this.expressionService.expressionsTriggeredBy(name);
529
599
  return _(triggeredPaths).map(p => this.dataItemsByExpression[p]).compact().uniq().value();
@@ -533,6 +603,11 @@ class AdapterConfig {
533
603
  const triggeredPaths = this.expressionService.expressionsTriggeredBy(name);
534
604
  return _(triggeredPaths).map(p => this.conditionsByExpression[p]).compact().uniq().value();
535
605
  }
606
+
607
+ triggeredDataEvents(name) {
608
+ const triggeredPaths = this.expressionService.expressionsTriggeredBy(name);
609
+ return _(triggeredPaths).map(p => this.dataEventsByExpression[p]).compact().uniq().value();
610
+ }
536
611
  }
537
612
 
538
613
  module.exports = AdapterConfig;
@@ -25,7 +25,7 @@ class ValueDecreaseFilter extends TransformState {
25
25
  }
26
26
 
27
27
  emitUpdate(context) {
28
- if (this.forceEmit || (this.value < this.previousValue && this.valueTime === this.lastSampleTime)) {
28
+ if (this.value < this.previousValue && this.valueTime === this.lastSampleTime) {
29
29
  this.forceEmit = false;
30
30
  this.lastEmitValue = this.value;
31
31
  this.lastEmitTime = this.lastSampleTime;
@@ -33,6 +33,14 @@ class ValueDecreaseFilter extends TransformState {
33
33
  }
34
34
  this.emit('update', false, this.lastSampleTime, context);
35
35
  }
36
+
37
+ setUnavailable(context, time) {
38
+ this.available = false;
39
+ this.previousValue = this.value;
40
+ this.previousValueTime = this.valueTime;
41
+ this.valueTime = time;
42
+ this.emit('unavailable', time, context);
43
+ }
36
44
  }
37
45
 
38
46
  module.exports = ValueDecreaseFilter;
@@ -25,7 +25,7 @@ class ValueIncreaseFilter extends TransformState {
25
25
  }
26
26
 
27
27
  emitUpdate(context) {
28
- if (this.forceEmit || (this.value > this.previousValue && this.valueTime === this.lastSampleTime)) {
28
+ if (this.value > this.previousValue && this.valueTime === this.lastSampleTime) {
29
29
  this.forceEmit = false;
30
30
  this.lastEmitValue = this.value;
31
31
  this.lastEmitTime = this.lastSampleTime;
@@ -33,6 +33,14 @@ class ValueIncreaseFilter extends TransformState {
33
33
  }
34
34
  this.emit('update', false, this.lastSampleTime, context);
35
35
  }
36
+
37
+ setUnavailable(context, time) {
38
+ this.available = false;
39
+ this.previousValue = this.value;
40
+ this.previousValueTime = this.valueTime;
41
+ this.valueTime = time;
42
+ this.emit('unavailable', time, context);
43
+ }
36
44
  }
37
45
 
38
46
  module.exports = ValueIncreaseFilter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machinemetrics/io-adapter-lib",
3
- "version": "2.34.0",
3
+ "version": "2.35.1",
4
4
  "description": "Configuration and engine implementation for MachineMetrics AdapterScripts and adapters",
5
5
  "main": "index.js",
6
6
  "license": "UNLICENSED",
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const expect = require('chai').expect;
5
+ const testUtils = require('../util/testUtils');
6
+
7
+ describe('Data events config tests', function () {
8
+ let config;
9
+ before(async () => {
10
+ config = await testUtils.loadConfig('data-events.yaml');
11
+ });
12
+
13
+ it('populates dataEventsByExpression table', function () {
14
+ expect(_.size(config.adapter.dataEventsByExpression)).to.eq(4);
15
+ expect(config.adapter.dataEventsByExpression['data-events.tool-change-1.payload']).to.eq('tool-change-1');
16
+ expect(config.adapter.dataEventsByExpression['data-events.tool-change-1.triggers.0']).to.eq('tool-change-1');
17
+ expect(config.adapter.dataEventsByExpression['data-events.tool-offset-change-1.payload']).to.eq('tool-offset-change-1');
18
+ expect(config.adapter.dataEventsByExpression['data-events.tool-offset-change-1.triggers.0']).to.eq('tool-offset-change-1');
19
+ });
20
+
21
+ it('parses data event structure', function () {
22
+ expect(_.size(config.adapter.dataEvents)).to.eq(2);
23
+
24
+ const ev1 = config.adapter.dataEvents['tool-change-1'];
25
+ expect(ev1.name).to.eq('tool-change-1');
26
+ expect(ev1.payload.rawExpression).to.eq('{}');
27
+ expect(ev1.payload.expression).to.exist;
28
+ expect(ev1.triggers).to.have.length(1);
29
+ expect(ev1.triggers[0].rawExpression).to.eq('tool-change-1');
30
+ expect(ev1.triggers[0].expression).to.exist;
31
+ expect(ev1.triggers[0].triggerVariables).to.deep.eq(['tool-change-1']);
32
+
33
+ const ev2 = config.adapter.dataEvents['tool-offset-change-1'];
34
+ expect(ev2.name).to.eq('tool-offset-change-1');
35
+ expect(ev2.payload.rawExpression).to.eq('{ x: XgeoT1, y: YgeoT1, z: ZgeoT1, xwear: XwearT1, ywear: YwearT1, zwear: ZwearT1 }');
36
+ expect(ev2.payload.expression).to.exist;
37
+ expect(ev2.triggers).to.have.length(1);
38
+ expect(ev2.triggers[0].rawExpression).to.eq('XGeoT1-chg or YGeoT1-chg or ZGeoT1-chg or XWearT1-chg or YWearT1-chg or ZWearT1-chg');
39
+ expect(ev2.triggers[0].expression).to.exist;
40
+ expect(ev2.triggers[0].triggerVariables).to.deep.eq([
41
+ 'XGeoT1-chg', 'YGeoT1-chg', 'ZGeoT1-chg', 'XWearT1-chg', 'YWearT1-chg', 'ZWearT1-chg',
42
+ ]);
43
+ });
44
+
45
+ it('resolves payload trigger variables', function () {
46
+ const svc = config.expressionService;
47
+ expect(svc.expressionTriggers('data-events.tool-change-1.payload')).to.deep.eq([]);
48
+ expect(svc.expressionTriggers('data-events.tool-offset-change-1.payload')).to.deep.eq([
49
+ 'XgeoT1', 'YgeoT1', 'ZgeoT1', 'XwearT1', 'YwearT1', 'ZwearT1',
50
+ ]);
51
+ });
52
+
53
+ it('resolves triggered data events by trigger variable', function () {
54
+ expect(config.adapter.triggeredDataEvents('tool-change-1')).to.deep.eq(['tool-change-1']);
55
+ expect(config.adapter.triggeredDataEvents('XGeoT1-chg')).to.deep.eq(['tool-offset-change-1']);
56
+ expect(config.adapter.triggeredDataEvents('YGeoT1-chg')).to.deep.eq(['tool-offset-change-1']);
57
+ expect(config.adapter.triggeredDataEvents('ZGeoT1-chg')).to.deep.eq(['tool-offset-change-1']);
58
+ expect(config.adapter.triggeredDataEvents('XWearT1-chg')).to.deep.eq(['tool-offset-change-1']);
59
+ expect(config.adapter.triggeredDataEvents('YWearT1-chg')).to.deep.eq(['tool-offset-change-1']);
60
+ expect(config.adapter.triggeredDataEvents('ZWearT1-chg')).to.deep.eq(['tool-offset-change-1']);
61
+ });
62
+
63
+ it('resolves triggered data events by payload variable', function () {
64
+ expect(config.adapter.triggeredDataEvents('XgeoT1')).to.deep.eq(['tool-offset-change-1']);
65
+ expect(config.adapter.triggeredDataEvents('YgeoT1')).to.deep.eq(['tool-offset-change-1']);
66
+ expect(config.adapter.triggeredDataEvents('ZgeoT1')).to.deep.eq(['tool-offset-change-1']);
67
+ expect(config.adapter.triggeredDataEvents('XwearT1')).to.deep.eq(['tool-offset-change-1']);
68
+ expect(config.adapter.triggeredDataEvents('YwearT1')).to.deep.eq(['tool-offset-change-1']);
69
+ expect(config.adapter.triggeredDataEvents('ZwearT1')).to.deep.eq(['tool-offset-change-1']);
70
+ });
71
+
72
+ it('returns empty array for variables not referenced in any data event', function () {
73
+ expect(config.adapter.triggeredDataEvents('unrelated')).to.deep.eq([]);
74
+ });
75
+ });
@@ -0,0 +1,25 @@
1
+ version: 2
2
+ device: local
3
+ declare-keys:
4
+ - tool-change-1
5
+ - XgeoT1
6
+ - YgeoT1
7
+ - ZgeoT1
8
+ - XwearT1
9
+ - YwearT1
10
+ - ZwearT1
11
+ - XGeoT1-chg
12
+ - YGeoT1-chg
13
+ - ZGeoT1-chg
14
+ - XWearT1-chg
15
+ - YWearT1-chg
16
+ - ZWearT1-chg
17
+ data-events:
18
+ tool-change-1:
19
+ payload: "{}"
20
+ triggers:
21
+ - tool-change-1
22
+ tool-offset-change-1:
23
+ payload: "{ x: XgeoT1, y: YgeoT1, z: ZgeoT1, xwear: XwearT1, ywear: YwearT1, zwear: ZwearT1 }"
24
+ triggers:
25
+ - XGeoT1-chg or YGeoT1-chg or ZGeoT1-chg or XWearT1-chg or YWearT1-chg or ZWearT1-chg
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ device: mtconnect-adapter
3
+ endpoint: localhost:8001
4
+ mtconnect-port: 8002
5
+ declare-keys:
6
+ - level
7
+ variables:
8
+ decreased:
9
+ - source: level
10
+ - value-decrease
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ device: mtconnect-adapter
3
+ endpoint: localhost:8001
4
+ mtconnect-port: 8002
5
+ declare-keys:
6
+ - level
7
+ variables:
8
+ increased:
9
+ - source: level
10
+ - value-increase
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const ValueDecrease = require('../../lib/transform').valueDecrease;
4
+ const EngineV2 = require('../../lib/engine/engineV2');
5
+ const Builder = require('../../lib/engine/transformBuilderV2');
4
6
  const testUtils = require('../util/testUtils');
5
7
 
6
8
  describe('value-decrease transform tests', function () {
@@ -12,7 +14,7 @@ describe('value-decrease transform tests', function () {
12
14
  [1, 2], [1, 4], [2, 6], [4, 10], [0, 14], [1, 15], [3, 16], [1, 21],
13
15
  ]);
14
16
  filter.validate([
15
- [true, 2], [false, 2], [false, 4], [false, 6], [false, 10], [true, 14], [false, 14],
17
+ [false, 2], [false, 4], [false, 6], [false, 10], [true, 14], [false, 14],
16
18
  [false, 15], [false, 16], [true, 21], [false, 21],
17
19
  ]);
18
20
  });
@@ -25,8 +27,33 @@ describe('value-decrease transform tests', function () {
25
27
  ['1', 2], ['1', 4], ['2', 6], ['10', 10], ['0', 14], ['1', 15], ['3', 16], ['1', 21],
26
28
  ]);
27
29
  filter.validate([
28
- [true, 2], [false, 2], [false, 4], [false, 6], [false, 10], [true, 14], [false, 14],
30
+ [false, 2], [false, 4], [false, 6], [false, 10], [true, 14], [false, 14],
29
31
  [false, 15], [false, 16], [true, 21], [false, 21],
30
32
  ]);
31
33
  });
32
34
  });
35
+
36
+ describe('value-decrease full engine config file tests', function () {
37
+ let config;
38
+ before(async function () {
39
+ config = await testUtils.loadConfig('transform/value-decrease.yml');
40
+ });
41
+
42
+ it('does not emit true when source sends UNAVAILABLE', function () {
43
+ const engine = new EngineV2(config);
44
+ const builder = new Builder(config);
45
+ builder.build(engine);
46
+
47
+ const source = testUtils.valueSource();
48
+ testUtils.attachEngineTransformValidator(engine, engine.variablePool.decreased, source);
49
+
50
+ source.sendValues('level', [
51
+ [1, 0], [2, 1], [0, 2], [1, 3], [2, 4], ['UNAVAILABLE', 5], [3, 6],
52
+ ]);
53
+
54
+ engine.validateFilter([
55
+ [false, 0], [false, 1], [true, 2], [false, 2], [false, 3], [false, 4],
56
+ [false, 6],
57
+ ]);
58
+ });
59
+ });
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const ValueIncrease = require('../../lib/transform').valueIncrease;
4
+ const EngineV2 = require('../../lib/engine/engineV2');
5
+ const Builder = require('../../lib/engine/transformBuilderV2');
4
6
  const testUtils = require('../util/testUtils');
5
7
 
6
8
  describe('value-increase transform tests', function () {
@@ -30,3 +32,28 @@ describe('value-increase transform tests', function () {
30
32
  ]);
31
33
  });
32
34
  });
35
+
36
+ describe('value-increase full engine config file tests', function () {
37
+ let config;
38
+ before(async function () {
39
+ config = await testUtils.loadConfig('transform/value-increase.yml');
40
+ });
41
+
42
+ it('does not emit true when source sends UNAVAILABLE', function () {
43
+ const engine = new EngineV2(config);
44
+ const builder = new Builder(config);
45
+ builder.build(engine);
46
+
47
+ const source = testUtils.valueSource();
48
+ testUtils.attachEngineTransformValidator(engine, engine.variablePool.increased, source);
49
+
50
+ source.sendValues('level', [
51
+ [1, 0], [2, 1], [0, 2], [1, 3], [2, 4], ['UNAVAILABLE', 5], [2, 6], [3, 7],
52
+ ]);
53
+
54
+ engine.validateFilter([
55
+ [true, 0], [false, 0], [true, 1], [false, 1], [false, 2], [true, 3], [false, 3],
56
+ [true, 4], [false, 4], [false, 6], [true, 7], [false, 7],
57
+ ]);
58
+ });
59
+ });