@rhizomatics/signalk-einklabel-plugin 1.3.0-beta14 → 1.3.0-beta15
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.
|
@@ -73,7 +73,16 @@ function withSessionWatchdog(conn, timeoutMs, forceClose) {
|
|
|
73
73
|
clearTimeout(timer);
|
|
74
74
|
if (killedError)
|
|
75
75
|
return; // already forced closed by the watchdog above
|
|
76
|
-
|
|
76
|
+
// The watchdog is off from here, so the disconnect needs its own limit: a disconnect that never
|
|
77
|
+
// finishes (e.g. over a Bluetooth link that's already gone) would otherwise hang the caller - and
|
|
78
|
+
// hide whatever error it was cleaning up after - with the connection and any claim still held.
|
|
79
|
+
try {
|
|
80
|
+
await (0, bleDiscovery_1.withDeadline)(conn.disconnect(), CLEANUP_TIMEOUT_MS, "disconnecting");
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error(`${pluginVersion_1.PLUGIN_NAME}: ${err.message} - forcing the connection closed`);
|
|
84
|
+
await forceClose().catch(() => { });
|
|
85
|
+
}
|
|
77
86
|
},
|
|
78
87
|
};
|
|
79
88
|
}
|
|
@@ -13,4 +13,12 @@ export interface RepaintScheduler {
|
|
|
13
13
|
* rather than waiting up to `intervalHours` for the next one).
|
|
14
14
|
*/
|
|
15
15
|
export declare function mostRecentScheduledSlot(now: Date, intervalHours: number, intervalMinute: number): Date;
|
|
16
|
+
/**
|
|
17
|
+
* Wraps `run` so only one call per key is in progress at a time. A call made while that key is busy
|
|
18
|
+
* is folded into a single follow-up (with the latest arguments), run once the current one finishes -
|
|
19
|
+
* but only if it succeeded (`run` resolved `true`). For repaints: newer data still gets shown promptly,
|
|
20
|
+
* but a label that's failing isn't hammered with a fresh round of retries straight after the last
|
|
21
|
+
* one gave up - the next scheduled trigger tries again instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare function oneAtATimePerKey<T>(keyOf: (arg: T) => string, run: (arg: T) => Promise<boolean>, onBusy?: (arg: T) => void): (arg: T) => Promise<void>;
|
|
16
24
|
export declare function startRepaintScheduler(app: ServerAPI, config: PluginConfig): RepaintScheduler;
|
package/dist/repaintScheduler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mostRecentScheduledSlot = mostRecentScheduledSlot;
|
|
4
|
+
exports.oneAtATimePerKey = oneAtATimePerKey;
|
|
4
5
|
exports.startRepaintScheduler = startRepaintScheduler;
|
|
5
6
|
const crypto_1 = require("crypto");
|
|
6
7
|
const path_1 = require("path");
|
|
@@ -393,6 +394,39 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
393
394
|
}
|
|
394
395
|
app.debug(succeeded ? `${label}: repainted (${repaintReason}, paint took ${paintDurationMs}ms)` : `${label}: repainted fallback warning`);
|
|
395
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Wraps `run` so only one call per key is in progress at a time. A call made while that key is busy
|
|
399
|
+
* is folded into a single follow-up (with the latest arguments), run once the current one finishes -
|
|
400
|
+
* but only if it succeeded (`run` resolved `true`). For repaints: newer data still gets shown promptly,
|
|
401
|
+
* but a label that's failing isn't hammered with a fresh round of retries straight after the last
|
|
402
|
+
* one gave up - the next scheduled trigger tries again instead.
|
|
403
|
+
*/
|
|
404
|
+
function oneAtATimePerKey(keyOf, run, onBusy = () => { }) {
|
|
405
|
+
const inProgress = new Set();
|
|
406
|
+
const followUps = new Map();
|
|
407
|
+
const call = async (arg) => {
|
|
408
|
+
const key = keyOf(arg);
|
|
409
|
+
if (inProgress.has(key)) {
|
|
410
|
+
followUps.set(key, arg);
|
|
411
|
+
onBusy(arg);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
inProgress.add(key);
|
|
415
|
+
let succeeded = false;
|
|
416
|
+
try {
|
|
417
|
+
succeeded = await run(arg);
|
|
418
|
+
}
|
|
419
|
+
finally {
|
|
420
|
+
inProgress.delete(key);
|
|
421
|
+
}
|
|
422
|
+
const followUp = followUps.get(key);
|
|
423
|
+
followUps.delete(key);
|
|
424
|
+
if (followUp !== undefined && succeeded) {
|
|
425
|
+
await call(followUp);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
return call;
|
|
429
|
+
}
|
|
396
430
|
function startRepaintScheduler(app, config) {
|
|
397
431
|
const state = loadState(app);
|
|
398
432
|
const unsubscribes = [];
|
|
@@ -404,15 +438,17 @@ function startRepaintScheduler(app, config) {
|
|
|
404
438
|
// (startup check, interval, subscription alike) funnels through this one gate.
|
|
405
439
|
const startedAt = Date.now();
|
|
406
440
|
const settleMs = (config.settleSeconds ?? 120) * 1000;
|
|
407
|
-
const repaint =
|
|
441
|
+
const repaint = oneAtATimePerKey((device) => device.friendlyName, (device) => repaintOnce(device), (device) => app.debug(`"${device.friendlyName}": already repainting - will check again once it's done`));
|
|
442
|
+
/** Returns whether every target was repainted (or was already up to date). */
|
|
443
|
+
const repaintOnce = async (device) => {
|
|
408
444
|
const elapsedMs = Date.now() - startedAt;
|
|
409
445
|
if (elapsedMs < settleMs) {
|
|
410
446
|
app.debug(`"${device.friendlyName}": still settling (${Math.round(elapsedMs / 1000)}s/${Math.round(settleMs / 1000)}s) - skipping repaint`);
|
|
411
|
-
return;
|
|
447
|
+
return false;
|
|
412
448
|
}
|
|
413
449
|
const targets = await resolveTargets(app, config, device);
|
|
414
450
|
if (targets.length === 0) {
|
|
415
|
-
return;
|
|
451
|
+
return false;
|
|
416
452
|
}
|
|
417
453
|
const results = await Promise.allSettled(targets.map((target) => considerRepaint(app, config, device, target, state, getApiUrl)));
|
|
418
454
|
results.forEach((result, i) => {
|
|
@@ -423,9 +459,11 @@ function startRepaintScheduler(app, config) {
|
|
|
423
459
|
// A single `forceRepaint` flag covers every target under `ALL_DEVICES` too - only clear it once
|
|
424
460
|
// every target has actually succeeded, so a target that failed still gets forced again next time
|
|
425
461
|
// instead of quietly reverting to ordinary hash-based dedup.
|
|
426
|
-
|
|
462
|
+
const allSucceeded = results.every((result) => result.status === "fulfilled");
|
|
463
|
+
if (device.advanced?.forceRepaint && allSucceeded) {
|
|
427
464
|
clearForceRepaint(app, device.friendlyName);
|
|
428
465
|
}
|
|
466
|
+
return allSucceeded;
|
|
429
467
|
};
|
|
430
468
|
const intervalDevices = config.devices.filter((device) => device.repaintTrigger === "interval");
|
|
431
469
|
if (intervalDevices.length > 0) {
|
package/docs/bluetooth.md
CHANGED
|
@@ -42,6 +42,10 @@ Bluetooth plugins can kick off scanning, and otherwise interfere with each other
|
|
|
42
42
|
|
|
43
43
|
If you're having problems with Bluetooth connections, make sure other plugins are well behaved, using BLE Manager where they can, and consider temporarily switching them off if needed to debug label connections.
|
|
44
44
|
|
|
45
|
+
## SignalK in Docker
|
|
46
|
+
|
|
47
|
+
Check for the `bluetooth` service working at both host level and inside the SignalK container. If there are stability issues, stop and disable the host level service (for example `sudo systemctl stop bluetooth` on a systemd controlled host).
|
|
48
|
+
|
|
45
49
|
## Stuck Bluetooth Adapters
|
|
46
50
|
|
|
47
51
|
Sometime Bluetooth adapters, and/or the Linux services that use them, can get into a 'stuck' state, where the only solution is to reboot the server (although unplugging and plugging the dongle may help). The best way to avoid this is using a known good dongle, and using BLE Manager in SignalK wherever possible.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizomatics/signalk-einklabel-plugin",
|
|
3
|
-
"version": "1.3.0-
|
|
3
|
+
"version": "1.3.0-beta15",
|
|
4
4
|
"description": "Display SignalK data on eInk Electronic Shelf Labels, includes working examples for tide clock and watch schedule.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ble",
|