-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
213 lines (201 loc) · 5.97 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
const jexl = require('jexl');
const _ = require('lodash');
const extractIdentifiers = require('./lib/extractIdentifiers.js');
const PLUGIN_ID = 'signalk-trigger';
const PLUGIN_NAME = 'Signalk trigger';
var triggers = [];
var unsubscribes = [];
module.exports = function(app) {
var plugin = {};
plugin.id = PLUGIN_ID;
plugin.name = PLUGIN_NAME;
plugin.description = 'A plugin to trigger events when a condition is met';
plugin.start = function(options, restartPlugin) {
app.debug('Plugin started');
plugin.options = options;
let contextMap = createContextMap(options.context);
// compile all triggers
if (options.triggers) {
options.triggers.forEach(trigger => {
let expr = jexl.compile(trigger.condition);
let identifiers = extractPaths(extractIdentifiers(expr), contextMap);
let contexts = options.context.map(mapping => {
return mapping.path.split('.')[0];
});
triggers.push({
expression: expr,
event: trigger.event,
context: contexts,
triggerType: trigger.triggerType,
previous: false,
identifiers: identifiers
});
});
// subscribe to all delta messages
app.signalk.on('delta', handleDelta);
unsubscribes.push(() => {
app.signalk.removeListener('delta', handleDelta);
});
app.setProviderStatus('Running');
} else {
app.setProviderStatus('No triggers set');
}
};
// check all triggers when a delta is received
function handleDelta(delta) {
//exclude notifications to avoid creating a lot of deltas when an event happens
if (delta.updates[0].values[0].path.split('.')[0] != 'notifications') {
let context = generateContext(plugin.options.context);
triggers.forEach(trigger => {
let newValue = trigger.expression.evalSync(context);
if (newValue == true && trigger.previous == false) {
if (trigger.triggerType != 'FALLING') {
notify(trigger.event, 'RISING', delta);
}
} else if (newValue == false && trigger.previous == true) {
if (trigger.triggerType != 'RISING') {
notify(trigger.event, 'FALLING', delta);
}
} else if (newValue == true) {
if (trigger.triggerType == 'ALWAYS') {
if (contextTest(trigger.context, delta.context)) {
paths = [];
delta.updates.forEach(update => {
update.values.forEach(value => {
if (value.path == '') {
paths = paths.concat(Object.keys(value.value));
} else {
paths.push(value.path);
}
});
});
if (includesAny(paths, trigger.identifiers)) {
notify(trigger.event, 'NO_CHANGE', delta);
}
}
}
}
trigger.previous = newValue;
});
}
}
// returns true if l1 contains any of the elements of l2
function includesAny(l1, l2) {
return l1.some(e => {
return l2.includes(e);
});
}
// compare list of contexts with deltacontext return true if deltaContext is matches with atleast one element of contexts
function contextTest(contexts, deltaContext) {
return contexts.some(context => {
return `vessels.${context}` == deltaContext;
});
}
function createContextMap(variables) {
let res = {};
variables.forEach(variable => {
res[variable.name] = variable.path;
});
return res;
}
function generateContext(variables) {
let res = {};
let context = app.getPath('vessels');
variables.forEach((variable) => {
res[variable.name] = _.get(context, variable.path);
});
return res;
}
// removes .value from the end of an identifier to match the signalk path in a delta
function extractPaths(identifiers, contextMap) {
let paths = identifiers.map(identifier => {
return contextMap[identifier];
});
return paths.map(identifier => {
let pathElements = identifier.split('.');
if (pathElements[pathElements.length - 1] == 'value') {
pathElements.pop();
}
pathElements.shift();
return pathElements.join('.');
});
}
function notify(event, type, delta) {
app.emit(event, {
event: event,
type: type,
value: delta
});
app.debug('event triggered: ' + type + ', ' + event);
}
plugin.stop = function() {
// Here we put logic we need when the plugin stops
app.debug('Plugin stopped');
triggers = [];
unsubscribes.forEach(f => f());
app.setProviderStatus('Stopped');
};
plugin.schema = {
title: PLUGIN_NAME,
type: 'object',
properties: {
context: {
type: 'array',
title: 'context',
items: {
type: 'object',
title: 'variable',
properties: {
path: {
type: 'string',
title: 'path'
},
name: {
type: 'string',
title: 'name'
}
}
}
},
triggers: {
type: 'array',
title: 'triggers',
items: {
type: 'object',
title: 'trigger',
required: ['condition', 'context', 'event'],
properties: {
condition: {
type: 'string',
title: 'condition'
},
event: {
type: 'string',
title: 'event'
},
triggerType: {
type: 'string',
title: 'trigger type',
enum: ['RISING', 'FALLING', 'BOTH', 'ALWAYS'],
enumNames: ['Rising edge', 'Falling edge', 'Both edges', 'for all deltas'],
default: 'BOTH'
}
}
}
}
}
};
plugin.uiSchema = {
triggers: {
'ui:options': {
orderable: false
}
},
context: {
'ui:options': {
orderable: false
}
}
};
return plugin;
};