signalk-automation-plugin 0.0.3 → 0.0.4
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/lib/engine.js +37 -9
- package/lib/runtime.js +16 -3
- package/package.json +1 -1
- package/public/app.js +14 -5
- package/public/index.html +2 -2
package/lib/engine.js
CHANGED
|
@@ -69,7 +69,7 @@ function evalClause (clause, ctx) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
if (Array.isArray(clause.all)) {
|
|
72
|
-
const parts = clause.all.map((c) => evalClause(c, ctx))
|
|
72
|
+
const parts = clause.all.map((c) => withLabel(c, evalClause(c, ctx)))
|
|
73
73
|
return {
|
|
74
74
|
pass: parts.every((p) => p.pass),
|
|
75
75
|
error: parts.find((p) => p.error)?.error || null,
|
|
@@ -78,10 +78,11 @@ function evalClause (clause, ctx) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
if (Array.isArray(clause.any)) {
|
|
81
|
-
const parts = clause.any.map((c) => evalClause(c, ctx))
|
|
81
|
+
const parts = clause.any.map((c) => withLabel(c, evalClause(c, ctx)))
|
|
82
|
+
const pass = parts.some((p) => p.pass)
|
|
82
83
|
return {
|
|
83
|
-
pass
|
|
84
|
-
error: parts.find((p) => p.error)?.error || null,
|
|
84
|
+
pass,
|
|
85
|
+
error: pass ? null : (parts.find((p) => p.error)?.error || null),
|
|
85
86
|
actual: parts,
|
|
86
87
|
clause: { any: true }
|
|
87
88
|
}
|
|
@@ -131,6 +132,18 @@ function evalClause (clause, ctx) {
|
|
|
131
132
|
return { pass: false, error: 'clause needs path, helper, zone, or run', actual: null }
|
|
132
133
|
}
|
|
133
134
|
const actual = getByPath(ctx.values, path)
|
|
135
|
+
const needsValue = ['is', 'equals', 'above', 'below', 'to'].some((k) =>
|
|
136
|
+
Object.prototype.hasOwnProperty.call(clause, k)
|
|
137
|
+
)
|
|
138
|
+
if (needsValue && (actual === undefined || actual === null)) {
|
|
139
|
+
return {
|
|
140
|
+
pass: false,
|
|
141
|
+
error: 'missing',
|
|
142
|
+
actual,
|
|
143
|
+
path,
|
|
144
|
+
expected: expectedOf(clause)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
134
147
|
let pass = false
|
|
135
148
|
if (Object.prototype.hasOwnProperty.call(clause, 'is')) {
|
|
136
149
|
pass = actual === clause.is
|
|
@@ -228,10 +241,20 @@ function clauseLabel (clause, result) {
|
|
|
228
241
|
|
|
229
242
|
function markPass (r) {
|
|
230
243
|
if (!r) return '✗'
|
|
231
|
-
if (r.error) return '✗'
|
|
244
|
+
if (r.error && !r.pass) return '✗'
|
|
232
245
|
return r.pass ? '✓' : '✗'
|
|
233
246
|
}
|
|
234
247
|
|
|
248
|
+
function appendClauseLines (lines, r, indent) {
|
|
249
|
+
if (!r) return
|
|
250
|
+
lines.push(indent + markPass(r) + ' ' + (r.label || ''))
|
|
251
|
+
const inner = r.clause && (r.clause.any || r.clause.all)
|
|
252
|
+
if (!inner || !Array.isArray(r.actual)) return
|
|
253
|
+
for (const part of r.actual) {
|
|
254
|
+
appendClauseLines(lines, part, indent + ' ')
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
function withLabel (clause, result) {
|
|
236
259
|
const row = result && typeof result === 'object' ? result : { pass: false }
|
|
237
260
|
row.clause = clause
|
|
@@ -279,9 +302,13 @@ async function evalListAll (list, ctx) {
|
|
|
279
302
|
return { pass, results }
|
|
280
303
|
}
|
|
281
304
|
|
|
305
|
+
function resultLabel (result) {
|
|
306
|
+
return result === 'failure' ? 'Failed' : 'OK'
|
|
307
|
+
}
|
|
308
|
+
|
|
282
309
|
function formatVerboseLog (auto, record) {
|
|
283
310
|
const name = (auto && (auto.alias || auto.id)) || (record && record.id) || 'automation'
|
|
284
|
-
const result = record.result
|
|
311
|
+
const result = resultLabel(record.result)
|
|
285
312
|
const head = name + ' → ' + result + (record.reason ? ' (' + record.reason + ')' : '')
|
|
286
313
|
const lines = [head]
|
|
287
314
|
if (record.firedBy) lines.push(' trigger: ' + record.firedBy)
|
|
@@ -297,7 +324,7 @@ function formatVerboseLog (auto, record) {
|
|
|
297
324
|
for (const b of record.branches || []) {
|
|
298
325
|
lines.push(' ' + (b.picked ? '→ ' : ' ') + b.alias + (b.picked ? '' : ''))
|
|
299
326
|
for (const c of b.conditions || []) {
|
|
300
|
-
lines
|
|
327
|
+
appendClauseLines(lines, c, ' ')
|
|
301
328
|
}
|
|
302
329
|
}
|
|
303
330
|
return lines.join('\n')
|
|
@@ -392,7 +419,7 @@ async function evaluateAutomation (auto, ctx) {
|
|
|
392
419
|
const record = {
|
|
393
420
|
ts: new Date(ctx.now || Date.now()).toISOString(),
|
|
394
421
|
id: auto.id,
|
|
395
|
-
result: '
|
|
422
|
+
result: 'ok',
|
|
396
423
|
trigger: ctx.trigger || null,
|
|
397
424
|
firedBy: triggerFiredLabel(ctx.trigger),
|
|
398
425
|
conditions: [],
|
|
@@ -416,7 +443,7 @@ async function evaluateAutomation (auto, ctx) {
|
|
|
416
443
|
record.conditions = cond.results
|
|
417
444
|
if ((auto.condition || []).length && !cond.pass) {
|
|
418
445
|
const failed = cond.results.find((r) => r.error)
|
|
419
|
-
record.result = failed ? 'failure' : '
|
|
446
|
+
record.result = failed ? 'failure' : 'ok'
|
|
420
447
|
record.reason = failed ? failed.error : 'condition not met'
|
|
421
448
|
return finishRecord(auto, record)
|
|
422
449
|
}
|
|
@@ -441,6 +468,7 @@ async function evaluateAutomation (auto, ctx) {
|
|
|
441
468
|
record.branches.push(row)
|
|
442
469
|
}
|
|
443
470
|
if (!picked) {
|
|
471
|
+
record.result = 'ok'
|
|
444
472
|
record.reason = 'no matching choose branch'
|
|
445
473
|
return finishRecord(auto, record)
|
|
446
474
|
}
|
package/lib/runtime.js
CHANGED
|
@@ -135,17 +135,24 @@ class Runtime {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
async handlePathChange (p, value, source) {
|
|
138
|
+
const previous = Object.prototype.hasOwnProperty.call(this.values, p)
|
|
139
|
+
? this.values[p]
|
|
140
|
+
: undefined
|
|
138
141
|
this.setPathValue(p, value, { $source: source })
|
|
139
|
-
await this.applyLatches(p, source)
|
|
142
|
+
await this.applyLatches(p, source, previous)
|
|
140
143
|
await this.kickPath(p, value)
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
async applyLatches (p, source) {
|
|
146
|
+
async applyLatches (p, source, previous) {
|
|
144
147
|
const fromSelf = source && String(source).includes(this.pluginId)
|
|
145
148
|
if (fromSelf) return
|
|
149
|
+
if (previous === undefined) return
|
|
150
|
+
const next = this.values[p]
|
|
151
|
+
if (engine.sameValue(previous, next)) return
|
|
152
|
+
if (asSwitch(previous) !== null && asSwitch(previous) === asSwitch(next)) return
|
|
146
153
|
for (const def of Object.values(this.doc.helpers)) {
|
|
147
154
|
if (def.latch_on_external_put === p && def.type === 'boolean') {
|
|
148
|
-
|
|
155
|
+
await this.setHelper(def.id, true)
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
158
|
}
|
|
@@ -439,6 +446,12 @@ class Runtime {
|
|
|
439
446
|
}
|
|
440
447
|
}
|
|
441
448
|
|
|
449
|
+
function asSwitch (v) {
|
|
450
|
+
if (v === true || v === 1 || v === '1' || v === 'on' || v === 'ON') return 1
|
|
451
|
+
if (v === false || v === 0 || v === '0' || v === 'off' || v === 'OFF') return 0
|
|
452
|
+
return null
|
|
453
|
+
}
|
|
454
|
+
|
|
442
455
|
function holdMs (auto) {
|
|
443
456
|
for (const t of auto.trigger || []) {
|
|
444
457
|
if (t.for) {
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -73,9 +73,15 @@ async function checkLogin () {
|
|
|
73
73
|
|
|
74
74
|
function el (id) { return document.getElementById(id) }
|
|
75
75
|
|
|
76
|
+
function displayResult (result) {
|
|
77
|
+
if (!result) return '—'
|
|
78
|
+
if (result === 'failure') return 'Failed'
|
|
79
|
+
return 'OK'
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
function badge (result) {
|
|
77
|
-
const
|
|
78
|
-
return '<span class="badge ' +
|
|
83
|
+
const klass = !result ? '' : result === 'failure' ? 'failure' : 'ok'
|
|
84
|
+
return '<span class="badge ' + klass + '">' + displayResult(result) + '</span>'
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
function fmtVal (v) {
|
|
@@ -136,15 +142,18 @@ function fmtRunRecord (t) {
|
|
|
136
142
|
function fmtTime (iso) {
|
|
137
143
|
if (!iso) return ''
|
|
138
144
|
try {
|
|
139
|
-
|
|
145
|
+
const d = new Date(iso)
|
|
146
|
+
if (Number.isNaN(d.getTime())) return iso
|
|
147
|
+
return new Intl.DateTimeFormat('nl-NL', {
|
|
148
|
+
timeZone: 'Europe/Amsterdam',
|
|
140
149
|
year: 'numeric',
|
|
141
150
|
month: '2-digit',
|
|
142
151
|
day: '2-digit',
|
|
143
152
|
hour: '2-digit',
|
|
144
153
|
minute: '2-digit',
|
|
145
154
|
second: '2-digit',
|
|
146
|
-
|
|
147
|
-
})
|
|
155
|
+
hourCycle: 'h23'
|
|
156
|
+
}).format(d)
|
|
148
157
|
} catch (_) { return iso }
|
|
149
158
|
}
|
|
150
159
|
|
package/public/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Signal K Automations</title>
|
|
7
7
|
<link rel="icon" href="icon.png" />
|
|
8
|
-
<link rel="stylesheet" href="style.css?v=
|
|
8
|
+
<link rel="stylesheet" href="style.css?v=20260918ok" />
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<header>
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
</div>
|
|
62
62
|
</section>
|
|
63
63
|
</main>
|
|
64
|
-
<script src="app.js?v=
|
|
64
|
+
<script src="app.js?v=20260918ok"></script>
|
|
65
65
|
</body>
|
|
66
66
|
</html>
|