@rhizomatics/signalk-einklabel-plugin 0.7.1 → 0.8.0
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/CHANGELOG.md +9 -0
- package/README.md +75 -24
- package/dist/config.d.ts +9 -0
- package/dist/config.js +10 -0
- package/dist/render/svgRenderer.js +2 -2
- package/dist/repaintScheduler.d.ts +10 -0
- package/dist/repaintScheduler.js +68 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# 0.8.0
|
|
2
|
+
|
|
3
|
+
- New `settle` time, configurable and defaulting to 120 seconds, to wait after plugin startup before attempting to access paths
|
|
4
|
+
- First few minutes of a SignalK server startup can be a mess of plugins missing their dependencies and logging errors.
|
|
5
|
+
- Schedule correctly picks up on previous schedule at startup, only repainting if the last scheduled time was overdue
|
|
6
|
+
- Path based subscriptions ignore temporarily missing paths, so displays don't waste their tiny batteries on flapping values
|
|
7
|
+
- Documentation now has a FAQ plus lots of other ESL/eInk hacking links.
|
|
8
|
+
|
|
9
|
+
|
|
1
10
|
# 0.7.1
|
|
2
11
|
|
|
3
12
|
- Fix path value retrieved for Image Fields when running in live plugin
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com)
|
|
8
8
|
[](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/LICENSE)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Fully working but limited vendor/product support and requires Linux for device access.
|
|
11
11
|
|
|
12
12
|
A SignalK plugin to display data from SignalK paths, APIs and plugins on Electronic Shelf Labels (ESL) over a Bluetooth Low Energy (BLE) connection using simple SVG templates.
|
|
13
13
|
|
|
@@ -48,7 +48,7 @@ Most of these requirements are about making SignalK work with Bluetooth Low Ener
|
|
|
48
48
|
|
|
49
49
|
5. Correct time zone set on server if local time is to be shown on display
|
|
50
50
|
|
|
51
|
-
-
|
|
51
|
+
- See [FAQ](#faq-timezone)
|
|
52
52
|
- If not set, everything will work, but you may see the wrong zone or not have daylight savings applied
|
|
53
53
|
|
|
54
54
|
Once you have all of that, it may be worth also installing [signalk-victron-ble](https://github.com/stefanor/signalk-victron-ble) or [bt-sensors-plugin](https://github.com/naugehyde/bt-sensors-plugin-sk) to pull in data from other sensors and equipment.
|
|
@@ -83,7 +83,7 @@ Use the standard configuration option in the SignalK menu for the plugin.
|
|
|
83
83
|
|
|
84
84
|

|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
### Scanning for Devices
|
|
87
87
|
|
|
88
88
|
Since these are ultra-low power devices, they don't respond instantly to either identify themselves or accept a new image. By default, both scanning and painting have time-outs to wait for a response, which can be altered in the plugin configuration or CLI argument.
|
|
89
89
|
|
|
@@ -91,14 +91,17 @@ One other quirk is that some devices respond with a different name at different
|
|
|
91
91
|
|
|
92
92
|
The plugin will optionally re-scan whenever it starts up, although this isn't essential once a label has been configured.
|
|
93
93
|
|
|
94
|
-
###
|
|
94
|
+
### Scheduling
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
There are two ways of scheduling scans:
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
#### Time Based
|
|
99
|
+
|
|
100
|
+
This will schedule at fixed hours of the day, for example 00:00/08:00/16:00 for an 8h schedule, and at a selected minutes past the hour. At plugin startup/restart, the devices will be repainted if they missed their last slot.
|
|
101
|
+
|
|
102
|
+
#### Path Subscription
|
|
103
|
+
|
|
104
|
+
The devices will be painted when the plugin starts, and then every time the selected SignalK path changes.
|
|
102
105
|
|
|
103
106
|
## Templating
|
|
104
107
|
|
|
@@ -171,21 +174,6 @@ Three font types are loaded by default, use the generic font family, or exact fo
|
|
|
171
174
|
- `sans-serif` - `Roboto`
|
|
172
175
|
- `monospace` - `Roboto Mono`
|
|
173
176
|
|
|
174
|
-
## Command Line Interface
|
|
175
|
-
|
|
176
|
-
To get fast feedback on templates and shelf devices without updating and configuring SignalK, a CLI call `esl-cli` is provided when the module is manually installed that has these commands. Use `--help` to get all the options.
|
|
177
|
-
|
|
178
|
-
- `vendors` - list supported vendors
|
|
179
|
-
- `scan` - report supported devices found from a BLE scan
|
|
180
|
-
|
|
181
|
-
See also the commands useful for debugging under [Developing Templates]
|
|
182
|
-
|
|
183
|
-
- `render` - transform an SVG template and data into a PNG
|
|
184
|
-
- `paint` - render an SVG template and data to a selected ESL
|
|
185
|
-
|
|
186
|
-
The width, height, vertical offset and colour palette for the device is taken from the internal register of devices, however can be overridden on the command line. This could be used to help you choose what size of label to buy, or to get an unsupported label working.
|
|
187
|
-
|
|
188
|
-
( The CLI can also be run from a checked out module, or by opening a terminal shell at `~/.signalk/node_modules/@rhizomatics/signalk-einklabel-plugin`, as `npx esl-cli command --args` )
|
|
189
177
|
|
|
190
178
|
## Vendors
|
|
191
179
|
|
|
@@ -214,6 +202,31 @@ The primary things managed and provided by the plugin are:
|
|
|
214
202
|
- Optional: left blank, the plugin probes the probable values in likelihood order at startup - `http://localhost:3000`, `http://localhost`, `https://localhost`. Set it explicitly to skip probing
|
|
215
203
|
- Either way, errors clearly if nothing responds (wrong port) or the probe is rejected (anonymous read access not enabled) - these endpoints must allow anonymous read access, since the plugin has no login flow
|
|
216
204
|
|
|
205
|
+
## Command Line Interface
|
|
206
|
+
|
|
207
|
+
To get fast feedback on templates and shelf devices without updating and configuring SignalK, a CLI call `esl-cli` is provided when the module is manually installed that has these commands. Use `--help` to get all the options.
|
|
208
|
+
|
|
209
|
+
- `vendors` - list supported vendors
|
|
210
|
+
- `scan` - report supported devices found from a BLE scan
|
|
211
|
+
|
|
212
|
+
See also the commands useful for debugging under [Developing Templates]
|
|
213
|
+
|
|
214
|
+
- `render` - transform an SVG template and data into a PNG
|
|
215
|
+
- `paint` - render an SVG template and data to a selected ESL
|
|
216
|
+
|
|
217
|
+
The width, height, vertical offset and colour palette for the device is taken from the internal register of devices, however can be overridden on the command line. This could be used to help you choose what size of label to buy, or to get an unsupported label working.
|
|
218
|
+
|
|
219
|
+
( The CLI can also be run from a checked out module, or by opening a terminal shell at `~/.signalk/node_modules/@rhizomatics/signalk-einklabel-plugin`, as `npx esl-cli command --args` )
|
|
220
|
+
|
|
221
|
+
### Scans from CLI
|
|
222
|
+
|
|
223
|
+
The command line tools, run from inside the `.signalk` directory, can be used to help troubleshoot
|
|
224
|
+
|
|
225
|
+
- Scan for longer, in this example 90 seconds
|
|
226
|
+
- `npx esl-cli scan -d 90`
|
|
227
|
+
- Scan for all BLE devices, whatever they are
|
|
228
|
+
- `npx esl-cli scan -a`
|
|
229
|
+
|
|
217
230
|
## Extending
|
|
218
231
|
|
|
219
232
|
### Hardware
|
|
@@ -226,6 +239,8 @@ Additional vendors and devices can be added by a separate npm package that imple
|
|
|
226
239
|
|
|
227
240
|
### Developing Templates
|
|
228
241
|
|
|
242
|
+
See the [Templating] section for more details.
|
|
243
|
+
|
|
229
244
|
Templates can be added to the configurable directory. [Inkscape](https://inkscape.org) free, open source, and recommended for editing templates, or your own favourite editor, or by hand in a text editor for hard core (or just tidying up the template side).
|
|
230
245
|
|
|
231
246
|

|
|
@@ -238,6 +253,7 @@ Inkscape adds its own metadata to images, which can be stripped off by exporting
|
|
|
238
253
|
|
|
239
254
|
Due to a limitation in the `resvg-wasm` library used to turn SVGs into images, the `font-family` is limited to `serif`,`sans-serif`,`monospace` or the exact name of one of the installed fonts - `Roboto` (sans serif), `Roboto Serif` or `Roboto Mono`. Inkscape has its own fonts, which won't match what's available in the SignalK plugin, so for more precise design, install [Roboto from Google](https://fonts.google.com/specimen/Roboto) via the web page, `brew` on MacOS or similar.
|
|
240
255
|
|
|
256
|
+
|
|
241
257
|
### Debugging Templates
|
|
242
258
|
|
|
243
259
|
The `esl-cli` can be used to debug and validate templates quickly:
|
|
@@ -286,3 +302,38 @@ extremes.0.level source=resources,resource=tides,path=extremes[0].level,
|
|
|
286
302
|
- `categories.json` - SignalK unit categories needed for `category=depth` type formatting
|
|
287
303
|
|
|
288
304
|
For example, `npx esl-cli fields -t templates/tide.svg -e examples` will show all the field data that will be populated from the example API, vessel and category data in the `examples` local directory.
|
|
305
|
+
|
|
306
|
+
## Frequently Asked Questions
|
|
307
|
+
|
|
308
|
+
### Sometimes values are missing on the display
|
|
309
|
+
|
|
310
|
+
If the plugin repaints a display at server startup, then the plugin that provides the data may not have started ( or in the case of `derived-data` the plugin that the plugin depends on! ) and unlike Home Assistant, there's no good way of sequencing the start of plugins.
|
|
311
|
+
|
|
312
|
+
Use the *settle* time, to impose a minimum wait between the eInk Label plugin being initialized, and it attempting to paint any displays, and increase this value if its still missing data.
|
|
313
|
+
|
|
314
|
+
### Times are showing incorrectly {#faq-timezone}
|
|
315
|
+
|
|
316
|
+
If times are in the wrong timezone, or don't have daylight savings applied correctly,
|
|
317
|
+
then check that the server itself (at the Linux level, not SignalK, which doesn't know)is configured for your timezone, assuming of course that you're a coastal sailor. Use `raspi-config` on a Raspberry Pi, or `timedatectl` on a Linux server.
|
|
318
|
+
|
|
319
|
+
If you're a global cruiser, then use something like [signalk-set-gps-timezone](https://github.com/hoeken/signalk-set-gps-timezone) to set the value in the operating system.
|
|
320
|
+
|
|
321
|
+
### The ESL signal is too weak from my SignalK server
|
|
322
|
+
|
|
323
|
+
Try a BLE proxy device, ESP32 is popular for this.
|
|
324
|
+
|
|
325
|
+
## Other ESL and General eInk Resources
|
|
326
|
+
|
|
327
|
+
- [Open ePaper Link](https://openepaperlink.de) - Alternative open source firmware to flash onto eInk shelf labels, with Home Assistant integration.
|
|
328
|
+
- [e-ink dashboard for Signal K](https://github.com/meri-imperiumi/dashboard) - Waveshare display based multi instrument display.
|
|
329
|
+
- [eInk Dashboard Modern SK](https://github.com/VladimirKalachikhin/e-inkDashboardModernSK) - SignalK dashboard for non-ESL eInk display.
|
|
330
|
+
- [esp32-esl-system](https://github.com/giobauermeister/esp32-esl-system) - Docker and ESP32 based system for updating ESLs.
|
|
331
|
+
- [hass-gicisky](https://github.com/eigger/hass-gicisky) - Home Assistant integration for Gicisky ESLs ( a similar vendor to Zhsunyco). Uses [imagespec](https://github.com/eigger/imagespec) for templating.
|
|
332
|
+
- [ha-panda](https://github.com/moryoav/ha-panda) - Home Assistant integration for Panda ESLs ( a similar vendor to Zhsunyco).
|
|
333
|
+
- [Dmitry.gr](https://dmitry.gr/?r=05.Projects&proj=29.%20eInk%20Price%20Tags) - Personal site of an ESL hacker
|
|
334
|
+
- [Aaron Christobel](https://www.youtube.com/@atc1441) - YouTube channel of an ESL hacker.
|
|
335
|
+
- [rbaron.net](https://rbaron.net/blog/2022/07/29/Daisy-chaining-multiple-electronic-shelf-labels) - Blog of an early ESL hacker.
|
|
336
|
+
- [Pimoroni](https://shop.pimoroni.com/collections/displays?tags=e-ink%20Displays) - All shapes and sizes of eInk displays, aimed at hackers, and with an [inky](https://github.com/pimoroni/inky) GitHub project to support them.
|
|
337
|
+
- [WaveShare](https://www.waveshare.com/product/displays/e-paper.htm) - Wide range of eInk displays for hardware projects, not limited to ESLs.
|
|
338
|
+
|
|
339
|
+
See also the [Boat Tech Directory](https://boat-tech-directory.rhizomatics.org.uk).
|
package/dist/config.d.ts
CHANGED
|
@@ -36,6 +36,15 @@ export interface PluginConfig {
|
|
|
36
36
|
paintConnectTimeoutSeconds: number;
|
|
37
37
|
/** How many times to attempt a repaint (including the first try) before giving up and reporting failure. */
|
|
38
38
|
paintRetries: number;
|
|
39
|
+
/**
|
|
40
|
+
* How long after plugin start to hold off on every repaint trigger (startup check, interval, and
|
|
41
|
+
* subscription alike) - the first minute or two of a SignalK server's life is a chaos of plugin
|
|
42
|
+
* dependency sequencing (e.g. `derived-data` may not have published `environment.moon.phaseName`
|
|
43
|
+
* yet), so a repaint attempted immediately at startup can render with missing/wrong data, and hash
|
|
44
|
+
* dedup (see `considerRepaint`) then means it silently stays that way until the underlying data
|
|
45
|
+
* happens to change again. Defaults to 120s - see `startRepaintScheduler`.
|
|
46
|
+
*/
|
|
47
|
+
settleSeconds: number;
|
|
39
48
|
/**
|
|
40
49
|
* Base URL of this SignalK server, used for: (1) a `signalk`-sourced numeric value's automatic unit
|
|
41
50
|
* conversion (`GET .../vessels/<context>/meta`, see `../pathMeta.ts`) unless `format=raw`, and (2) an
|
package/dist/config.js
CHANGED
|
@@ -29,6 +29,7 @@ function defaultConfig() {
|
|
|
29
29
|
scanDurationSeconds: 20,
|
|
30
30
|
paintConnectTimeoutSeconds: 30,
|
|
31
31
|
paintRetries: 3,
|
|
32
|
+
settleSeconds: 120,
|
|
32
33
|
devices: [],
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -153,6 +154,15 @@ function configSchema(app, discovered = []) {
|
|
|
153
154
|
minimum: 1,
|
|
154
155
|
default: defaults.paintRetries,
|
|
155
156
|
},
|
|
157
|
+
settleSeconds: {
|
|
158
|
+
type: "number",
|
|
159
|
+
title: "Settle time after plugin start (seconds)",
|
|
160
|
+
description: "Holds off every repaint (startup check, interval, and subscription alike) until this long after the plugin starts - " +
|
|
161
|
+
"the first minute or two of SignalK startup is a chaos of plugin dependency sequencing, so data a template needs " +
|
|
162
|
+
"(e.g. from the derived-data plugin) may not be published yet.",
|
|
163
|
+
minimum: 0,
|
|
164
|
+
default: defaults.settleSeconds,
|
|
165
|
+
},
|
|
156
166
|
signalkApiUrl: {
|
|
157
167
|
type: "string",
|
|
158
168
|
title: "SignalK API base URL (leave blank to auto-detect)",
|
|
@@ -135,8 +135,8 @@ class SvgRenderer {
|
|
|
135
135
|
const dirProblem = (0, assets_1.describeAssetsDirProblem)(templatesDir, bundledTemplatesDir, binding.assets);
|
|
136
136
|
console.error(key
|
|
137
137
|
? `${pluginVersion_1.PLUGIN_NAME}: image "${descElement.textContent}" has no asset file for value "${key}" in "${binding.assets}"`
|
|
138
|
-
: `${pluginVersion_1.PLUGIN_NAME}: image "${descElement.textContent}" resolved to
|
|
139
|
-
console.error(`${pluginVersion_1.PLUGIN_NAME}: ${dirProblem ?? `assets directory for "${binding.assets}"
|
|
138
|
+
: `${pluginVersion_1.PLUGIN_NAME}: image "${descElement.textContent}" resolved to empty value to select asset`);
|
|
139
|
+
console.error(`${pluginVersion_1.PLUGIN_NAME}: ${dirProblem ?? `assets directory for "${binding.assets}" is present`}`);
|
|
140
140
|
element.parentNode?.removeChild(element);
|
|
141
141
|
continue;
|
|
142
142
|
}
|
|
@@ -3,4 +3,14 @@ import { PluginConfig } from "./config";
|
|
|
3
3
|
export interface RepaintScheduler {
|
|
4
4
|
stop(): void;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The most recent wall-clock instant at or before `now` that an `interval`-triggered device's own
|
|
8
|
+
* periodic check (below, in `startRepaintScheduler`) would fire at - every `intervalHours` hours,
|
|
9
|
+
* at `intervalMinute` minutes past the hour (e.g. hours=8,minute=0 -> 00:00, 08:00, 16:00 each day).
|
|
10
|
+
* Used only by the deferred startup catch-up check to tell an interval device that's already been
|
|
11
|
+
* painted for its current slot (skip - the regular periodic check will catch the *next* slot in due
|
|
12
|
+
* course) from one that's overdue (e.g. the plugin was down across a scheduled slot - paint now
|
|
13
|
+
* rather than waiting up to `intervalHours` for the next one).
|
|
14
|
+
*/
|
|
15
|
+
export declare function mostRecentScheduledSlot(now: Date, intervalHours: number, intervalMinute: number): Date;
|
|
6
16
|
export declare function startRepaintScheduler(app: ServerAPI, config: PluginConfig): RepaintScheduler;
|
package/dist/repaintScheduler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mostRecentScheduledSlot = mostRecentScheduledSlot;
|
|
3
4
|
exports.startRepaintScheduler = startRepaintScheduler;
|
|
4
5
|
const crypto_1 = require("crypto");
|
|
5
6
|
const path_1 = require("path");
|
|
@@ -17,6 +18,23 @@ const resolveApiUrl_1 = require("./resolveApiUrl");
|
|
|
17
18
|
const pluginVersion_1 = require("./pluginVersion");
|
|
18
19
|
const INTERVAL_POLL_MS = 60000;
|
|
19
20
|
const SUBSCRIPTION_DEBOUNCE_MS = 2000;
|
|
21
|
+
/**
|
|
22
|
+
* The most recent wall-clock instant at or before `now` that an `interval`-triggered device's own
|
|
23
|
+
* periodic check (below, in `startRepaintScheduler`) would fire at - every `intervalHours` hours,
|
|
24
|
+
* at `intervalMinute` minutes past the hour (e.g. hours=8,minute=0 -> 00:00, 08:00, 16:00 each day).
|
|
25
|
+
* Used only by the deferred startup catch-up check to tell an interval device that's already been
|
|
26
|
+
* painted for its current slot (skip - the regular periodic check will catch the *next* slot in due
|
|
27
|
+
* course) from one that's overdue (e.g. the plugin was down across a scheduled slot - paint now
|
|
28
|
+
* rather than waiting up to `intervalHours` for the next one).
|
|
29
|
+
*/
|
|
30
|
+
function mostRecentScheduledSlot(now, intervalHours, intervalMinute) {
|
|
31
|
+
const slot = new Date(now);
|
|
32
|
+
slot.setHours(Math.floor(now.getHours() / intervalHours) * intervalHours, intervalMinute, 0, 0);
|
|
33
|
+
if (slot.getTime() > now.getTime()) {
|
|
34
|
+
slot.setHours(slot.getHours() - intervalHours);
|
|
35
|
+
}
|
|
36
|
+
return slot;
|
|
37
|
+
}
|
|
20
38
|
function statePath(app) {
|
|
21
39
|
return (0, path_1.join)(app.getDataDirPath(), "repaint-state.json");
|
|
22
40
|
}
|
|
@@ -168,7 +186,7 @@ async function considerRepaint(app, config, device, state, getApiUrl) {
|
|
|
168
186
|
await driver.paint(bitmap, { address: model.address, aesKey: device.aesKey, connectTimeoutMs });
|
|
169
187
|
paintDurationMs = Date.now() - startedAt;
|
|
170
188
|
});
|
|
171
|
-
state[device.friendlyName] = { hash };
|
|
189
|
+
state[device.friendlyName] = { hash, repaintedAt: Date.now() };
|
|
172
190
|
saveState(app, state);
|
|
173
191
|
if (device.forceRepaint) {
|
|
174
192
|
clearForceRepaint(app, device.friendlyName);
|
|
@@ -179,7 +197,21 @@ function startRepaintScheduler(app, config) {
|
|
|
179
197
|
const state = loadState(app);
|
|
180
198
|
const unsubscribes = [];
|
|
181
199
|
const getApiUrl = (0, resolveApiUrl_1.createApiUrlResolver)(config.signalkApiUrl);
|
|
182
|
-
|
|
200
|
+
// The first minute or two of a SignalK server's life is a chaos of plugin dependency sequencing -
|
|
201
|
+
// a repaint attempted before other plugins (e.g. derived-data) have published the data a template
|
|
202
|
+
// needs renders with missing/wrong values, and hash dedup (see `considerRepaint`) then means it
|
|
203
|
+
// silently stays that way until the underlying data happens to change again. So every trigger
|
|
204
|
+
// (startup check, interval, subscription alike) funnels through this one gate.
|
|
205
|
+
const startedAt = Date.now();
|
|
206
|
+
const settleMs = (config.settleSeconds ?? 120) * 1000;
|
|
207
|
+
const repaint = (device) => {
|
|
208
|
+
const elapsedMs = Date.now() - startedAt;
|
|
209
|
+
if (elapsedMs < settleMs) {
|
|
210
|
+
app.debug(`"${device.friendlyName}": still settling (${Math.round(elapsedMs / 1000)}s/${Math.round(settleMs / 1000)}s) - skipping repaint`);
|
|
211
|
+
return Promise.resolve();
|
|
212
|
+
}
|
|
213
|
+
return considerRepaint(app, config, device, state, getApiUrl).catch((err) => app.debug(`"${device.friendlyName}": repaint failed: ${err.message}`));
|
|
214
|
+
};
|
|
183
215
|
const intervalDevices = config.devices.filter((device) => device.repaintTrigger === "interval");
|
|
184
216
|
if (intervalDevices.length > 0) {
|
|
185
217
|
const timer = setInterval(() => {
|
|
@@ -197,15 +229,43 @@ function startRepaintScheduler(app, config) {
|
|
|
197
229
|
for (const device of config.devices) {
|
|
198
230
|
if (device.repaintTrigger === "subscription" && device.triggerPath) {
|
|
199
231
|
const stream = app.streambundle.getSelfStream(device.triggerPath).debounce(SUBSCRIPTION_DEBOUNCE_MS);
|
|
200
|
-
|
|
232
|
+
// A path can report a transient `null`/missing value (e.g. a sensor briefly drops out) then go
|
|
233
|
+
// right back to what it was before - e.g. 1111, then missing, then 1111 again. Ignoring the
|
|
234
|
+
// missing emission entirely (rather than repainting for it) means it's never actually painted,
|
|
235
|
+
// so when the value comes back the same as before, `considerRepaint`'s hash dedup sees nothing
|
|
236
|
+
// changed since the last real paint and skips too - net effect: no repaint at all for this
|
|
237
|
+
// blip, instead of one flickering the display blank/wrong and a second restoring it.
|
|
238
|
+
const unsub = stream.onValue((value) => {
|
|
239
|
+
if (value !== null && value !== undefined)
|
|
240
|
+
void repaint(device);
|
|
241
|
+
});
|
|
201
242
|
unsubscribes.push(unsub);
|
|
202
243
|
}
|
|
203
244
|
}
|
|
204
|
-
// Check every device once
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
245
|
+
// Check every device once, after the settle window. Deferred (rather than run immediately) so a
|
|
246
|
+
// subscription-triggered device whose path is slow-changing (e.g. tide/moon data that might not
|
|
247
|
+
// change again for hours) still gets its first real paint promptly once settled, instead of
|
|
248
|
+
// waiting on that path's next unrelated update - it's then left alone until the path changes
|
|
249
|
+
// again, same as always. An interval-triggered device, though, only gets this catch-up paint if
|
|
250
|
+
// it's actually overdue - already painted for the current scheduled slot (e.g. plugin restarted
|
|
251
|
+
// mid-way through its interval) just waits for the regular periodic check above to hit the *next*
|
|
252
|
+
// slot, rather than jumping the gun with an extra unscheduled paint every time the plugin restarts.
|
|
253
|
+
const startupCheckTimer = setTimeout(() => {
|
|
254
|
+
for (const device of config.devices) {
|
|
255
|
+
if (device.repaintTrigger === "interval" && !device.forceRepaint) {
|
|
256
|
+
const hours = device.intervalHours ?? 1;
|
|
257
|
+
const minute = device.intervalMinute ?? 0;
|
|
258
|
+
const dueSlot = mostRecentScheduledSlot(new Date(), hours, minute);
|
|
259
|
+
const repaintedAt = state[device.friendlyName]?.repaintedAt;
|
|
260
|
+
if (repaintedAt !== undefined && repaintedAt >= dueSlot.getTime()) {
|
|
261
|
+
app.debug(`"${device.friendlyName}": already painted for the current ${hours}h schedule slot - skipping startup catch-up`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
void repaint(device);
|
|
266
|
+
}
|
|
267
|
+
}, settleMs);
|
|
268
|
+
unsubscribes.push(() => clearTimeout(startupCheckTimer));
|
|
209
269
|
return {
|
|
210
270
|
stop() {
|
|
211
271
|
for (const unsubscribe of unsubscribes)
|