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 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: parts.some((p) => p.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 || 'skipped'
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.push(' ' + markPass(c) + ' ' + (c.label || ''))
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: 'skipped',
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' : 'skipped'
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
- if (this.values[p]) await this.setHelper(def.id, true)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-automation-plugin",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Home Assistant-like YAML automations for Signal K",
5
5
  "main": "plugin/index.js",
6
6
  "keywords": [
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 r = result || '—'
78
- return '<span class="badge ' + (result || '') + '">' + r + '</span>'
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
- return new Date(iso).toLocaleString('nl-NL', {
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
- hour12: false
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=20260915h24" />
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=20260915h24"></script>
64
+ <script src="app.js?v=20260918ok"></script>
65
65
  </body>
66
66
  </html>