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 +2 -1
- package/lib/runtime.js +47 -2
- package/lib/store.js +2 -1
- package/package.json +1 -1
- package/plugin/index.js +8 -0
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
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
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),
|