signalk-automation-plugin 0.0.10 → 0.0.11

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/README.md CHANGED
@@ -31,8 +31,9 @@ Not Node-RED. Not a Home Assistant YAML clone. Actions are Signal K PUTs, helper
31
31
  | Allowed scripts directory | Only relative `run.file` paths under this dir |
32
32
  | Decision records to keep | Ring buffer per automation |
33
33
  | Script timeout | Seconds |
34
+ | Startup silence | Seconds after start (default 60, 0 = off) during which path triggers do not fire; values are still recorded |
34
35
 
35
- Do not paste automations into the admin form. Runtime state is `~/.signalk/plugin-config-data/signalk-automation-plugin/` (`state.json`, `live/`, `traces/`).
36
+ Do not paste automations into the admin form. Runtime state is `~/.signalk/plugin-config-data/signalk-automation-plugin/` (`state.json`, `live/`, `traces/`). `state.json` also keeps the last seen value of each trigger path (`pathValues`), so the same value republished after a restart does not re-trigger.
36
37
 
37
38
  ## Example
38
39
 
package/lib/runtime.js CHANGED
@@ -17,6 +17,9 @@ class Runtime {
17
17
  this.scriptsDir = opts.scriptsDir
18
18
  this.traceCount = opts.traceCount || 20
19
19
  this.scriptTimeoutMs = (opts.scriptTimeoutSeconds || 30) * 1000
20
+ const silence = Number(opts.startupSilenceSeconds)
21
+ this.startupSilenceMs = (Number.isFinite(silence) && silence >= 0 ? silence : 0) * 1000
22
+ this.startedAt = null
20
23
  this.now = opts.now || (() => Date.now())
21
24
  this.sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))
22
25
  this.putFn = opts.put
@@ -39,6 +42,8 @@ class Runtime {
39
42
 
40
43
  load () {
41
44
  this.state = store.loadState(this.dataDir)
45
+ if (!this.state.pathValues || typeof this.state.pathValues !== 'object') this.state.pathValues = {}
46
+ this.startedAt = this.now()
42
47
  const live = store.liveDir(this.dataDir)
43
48
  const from = fs.existsSync(live) ? live : this.automationsDir
44
49
  this.doc = yaml.loadYamlDir(from)
@@ -145,13 +150,53 @@ class Runtime {
145
150
  }
146
151
  }
147
152
 
153
+ triggerPaths () {
154
+ const paths = new Set()
155
+ for (const auto of this.doc.automations) {
156
+ for (const t of auto.trigger || []) {
157
+ if (t && typeof t.path === 'string') paths.add(t.path)
158
+ }
159
+ }
160
+ return paths
161
+ }
162
+
163
+ inStartupSilence () {
164
+ if (this.startedAt == null || !(this.startupSilenceMs > 0)) return false
165
+ return this.now() - this.startedAt < this.startupSilenceMs
166
+ }
167
+
168
+ rememberPathValue (p, value) {
169
+ if (!this.triggerPaths().has(p)) return
170
+ const pv = this.state.pathValues || (this.state.pathValues = {})
171
+ const has = Object.prototype.hasOwnProperty.call(pv, p)
172
+ if (has && engine.sameValue(pv[p], value)) return
173
+ if (value === undefined) return
174
+ try {
175
+ pv[p] = JSON.parse(JSON.stringify(value))
176
+ } catch (_) {
177
+ return
178
+ }
179
+ store.saveState(this.dataDir, this.state)
180
+ }
181
+
148
182
  async handlePathChange (p, value, source) {
149
- const previous = Object.prototype.hasOwnProperty.call(this.values, p)
183
+ let previous = Object.prototype.hasOwnProperty.call(this.values, p)
150
184
  ? this.values[p]
151
185
  : undefined
186
+ const inMemory = previous !== undefined
187
+ const pv = this.state.pathValues || {}
188
+ if (!inMemory && Object.prototype.hasOwnProperty.call(pv, p)) {
189
+ // Value remembered from before a restart.
190
+ previous = pv[p]
191
+ }
152
192
  this.setPathValue(p, value, { $source: source })
153
- await this.applyLatches(p, source, previous)
193
+ this.rememberPathValue(p, value)
194
+ await this.applyLatches(p, source, inMemory ? previous : undefined)
154
195
  if (engine.valueUnchanged(previous, value)) return
196
+ if (this.inStartupSilence()) {
197
+ this.log.debug('startup silence: not triggering on ' + p)
198
+ return
199
+ }
155
200
  await this.kickPath(p, value)
156
201
  }
157
202
 
package/lib/store.js CHANGED
@@ -9,7 +9,8 @@ function emptyState () {
9
9
  enabled: {},
10
10
  verbose: {},
11
11
  activeSha: null,
12
- lastRun: {}
12
+ lastRun: {},
13
+ pathValues: {}
13
14
  }
14
15
  }
15
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-automation-plugin",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Home Assistant-like YAML automations for Signal K",
5
5
  "main": "plugin/index.js",
6
6
  "keywords": [
package/plugin/index.js CHANGED
@@ -84,6 +84,13 @@ module.exports = function (app) {
84
84
  title: 'Script timeout (seconds)',
85
85
  default: 30,
86
86
  minimum: 1
87
+ },
88
+ startupSilence: {
89
+ type: 'number',
90
+ title: 'Startup silence (seconds)',
91
+ description: 'After start, path triggers do not fire for this many seconds (values are still recorded). 0 = off.',
92
+ default: 60,
93
+ minimum: 0
87
94
  }
88
95
  }
89
96
  }
@@ -424,6 +431,7 @@ module.exports = function (app) {
424
431
  scriptsDir: options.scriptsDir || '',
425
432
  traceCount: options.traceCount,
426
433
  scriptTimeoutSeconds: options.scriptTimeoutSeconds,
434
+ startupSilenceSeconds: options.startupSilence == null ? 60 : options.startupSilence,
427
435
  put: putPath,
428
436
  notify,
429
437
  onHelper: (id, value) => emitHelper(id, value),