@rhizomatics/signalk-einklabel-plugin 1.3.0-beta3 → 1.3.0-beta5
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 +1 -0
- package/dist/devices/bleBackend.d.ts +17 -0
- package/dist/devices/bleBackend.js +53 -0
- package/dist/repaintScheduler.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -25,6 +25,23 @@ export interface BleBackend {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function withSessionWatchdog(conn: GattConnection, timeoutMs: number, forceClose: () => Promise<void>): GattConnection;
|
|
27
27
|
export declare function nodeBleBackend(): BleBackend;
|
|
28
|
+
/**
|
|
29
|
+
* Waits for the BLE Manager to know about `address` before a GATT connect is attempted against it -
|
|
30
|
+
* mirrors `getOrDiscoverDevice` in `bleDiscovery.ts`, which gives `nodeBleBackend` the same guarantee
|
|
31
|
+
* against BlueZ directly. Without this, a specifically-addressed device that the manager hasn't seen
|
|
32
|
+
* yet (e.g. this plugin's own `scanOnStart` is off and no other BLE plugin happens to be scanning)
|
|
33
|
+
* would sit on `bleApi.connectGATT()` until that call's own `timeoutMs` gives up, rather than the
|
|
34
|
+
* plugin quietly rediscovering it the way the direct-BlueZ backend already does.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureDeviceVisible(bleApi: BLEApi, pluginId: string, address: string, timeoutMs: number): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Runs `fn` only once every earlier caller has finished, so paints to different devices never hold
|
|
39
|
+
* BLE Manager GATT slots at the same time. The server's local provider defaults to just a couple of
|
|
40
|
+
* slots shared with every other BLE plugin (e.g. Bluetti), so two concurrent connects can leave the
|
|
41
|
+
* second with none free - which the server reports as the misleading "No provider with GATT support
|
|
42
|
+
* can see <mac>".
|
|
43
|
+
*/
|
|
44
|
+
export declare function exclusiveBleManagerAccess<T>(fn: () => Promise<T>): Promise<T>;
|
|
28
45
|
/**
|
|
29
46
|
* `bleApi.connectGATT()` has no timeout of its own, same story as node-ble's `Device#connect()` (see
|
|
30
47
|
* `connectWithTimeout` in `bleDiscovery.ts`) - races it against `timeoutMs`.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.withSessionWatchdog = withSessionWatchdog;
|
|
4
4
|
exports.nodeBleBackend = nodeBleBackend;
|
|
5
|
+
exports.ensureDeviceVisible = ensureDeviceVisible;
|
|
6
|
+
exports.exclusiveBleManagerAccess = exclusiveBleManagerAccess;
|
|
5
7
|
exports.bleApiBackend = bleApiBackend;
|
|
6
8
|
const pluginVersion_1 = require("../pluginVersion");
|
|
7
9
|
const bleDiscovery_1 = require("./bleDiscovery");
|
|
@@ -137,6 +139,48 @@ function nodeBleBackend() {
|
|
|
137
139
|
},
|
|
138
140
|
};
|
|
139
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Waits for the BLE Manager to know about `address` before a GATT connect is attempted against it -
|
|
144
|
+
* mirrors `getOrDiscoverDevice` in `bleDiscovery.ts`, which gives `nodeBleBackend` the same guarantee
|
|
145
|
+
* against BlueZ directly. Without this, a specifically-addressed device that the manager hasn't seen
|
|
146
|
+
* yet (e.g. this plugin's own `scanOnStart` is off and no other BLE plugin happens to be scanning)
|
|
147
|
+
* would sit on `bleApi.connectGATT()` until that call's own `timeoutMs` gives up, rather than the
|
|
148
|
+
* plugin quietly rediscovering it the way the direct-BlueZ backend already does.
|
|
149
|
+
*/
|
|
150
|
+
async function ensureDeviceVisible(bleApi, pluginId, address, timeoutMs) {
|
|
151
|
+
const known = await bleApi.getDevice(address).catch(() => null);
|
|
152
|
+
if (known)
|
|
153
|
+
return;
|
|
154
|
+
const seen = await new Promise((resolve) => {
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
unsubscribe();
|
|
157
|
+
resolve(false);
|
|
158
|
+
}, timeoutMs);
|
|
159
|
+
const unsubscribe = bleApi.onAdvertisement(pluginId, (adv) => {
|
|
160
|
+
if (adv.mac !== address)
|
|
161
|
+
return;
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
unsubscribe();
|
|
164
|
+
resolve(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
if (!seen) {
|
|
168
|
+
throw new Error(`device ${address} isn't visible to the SignalK BLE Manager yet (out of range, asleep, or never seen) after waiting ${timeoutMs}ms`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
let bleManagerQueue = Promise.resolve();
|
|
172
|
+
/**
|
|
173
|
+
* Runs `fn` only once every earlier caller has finished, so paints to different devices never hold
|
|
174
|
+
* BLE Manager GATT slots at the same time. The server's local provider defaults to just a couple of
|
|
175
|
+
* slots shared with every other BLE plugin (e.g. Bluetti), so two concurrent connects can leave the
|
|
176
|
+
* second with none free - which the server reports as the misleading "No provider with GATT support
|
|
177
|
+
* can see <mac>".
|
|
178
|
+
*/
|
|
179
|
+
function exclusiveBleManagerAccess(fn) {
|
|
180
|
+
const run = bleManagerQueue.then(fn, fn);
|
|
181
|
+
bleManagerQueue = run.catch(() => { });
|
|
182
|
+
return run;
|
|
183
|
+
}
|
|
140
184
|
/**
|
|
141
185
|
* `bleApi.connectGATT()` has no timeout of its own, same story as node-ble's `Device#connect()` (see
|
|
142
186
|
* `connectWithTimeout` in `bleDiscovery.ts`) - races it against `timeoutMs`.
|
|
@@ -149,6 +193,7 @@ function bleApiBackend(bleApi, pluginId) {
|
|
|
149
193
|
// "already claimed" until the server restarts - see signalk-bluetti-plugin's BleManagerDevice
|
|
150
194
|
// for the same defensive call. A no-op if we don't currently hold the claim.
|
|
151
195
|
await bleApi.releaseGATTDevice(address, pluginId).catch(() => { });
|
|
196
|
+
await ensureDeviceVisible(bleApi, pluginId, address, DEVICE_DISCOVERY_TIMEOUT_MS);
|
|
152
197
|
const connecting = bleApi.connectGATT(address, pluginId);
|
|
153
198
|
let timedOut = false;
|
|
154
199
|
const conn = await Promise.race([connecting, (0, bleDiscovery_1.sleep)(timeoutMs).then(() => void (timedOut = true))]);
|
|
@@ -179,6 +224,14 @@ function bleApiBackend(bleApi, pluginId) {
|
|
|
179
224
|
// `BLEDeviceInfo` (from `getDevices()`/`getDevice()`) carries mac/name/rssi/seenBy but not
|
|
180
225
|
// manufacturer data (see `BLEDeviceInfoSchema` in `@signalk/server-api`'s `ble-schemas.ts`) -
|
|
181
226
|
// only the streamed `BLEAdvertisement` does. So this always actively waits on the stream.
|
|
227
|
+
//
|
|
228
|
+
// Same defensive release as `connectGatt`, and just as necessary here: a device the BLE Manager
|
|
229
|
+
// still thinks *we* hold a GATT claim on (e.g. from a crash/reload mid-paint, before this call
|
|
230
|
+
// ever reaches `connectGatt` below) stops advertising while claimed - which would otherwise wedge
|
|
231
|
+
// this wait forever, since nothing else in this path ever calls `connectGatt` (and so never gets a
|
|
232
|
+
// chance to release the stale claim) unless a fresh advertisement shows up first. A no-op if we
|
|
233
|
+
// don't currently hold the claim.
|
|
234
|
+
await bleApi.releaseGATTDevice(address, pluginId).catch(() => { });
|
|
182
235
|
return new Promise((resolve) => {
|
|
183
236
|
const timer = setTimeout(() => {
|
|
184
237
|
unsubscribe();
|
package/dist/repaintScheduler.js
CHANGED
|
@@ -345,15 +345,16 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
345
345
|
bitmap = await renderer.render(fallbackPath, { meta: { repainted: new Date().toISOString(), plugin_version: pluginVersion_1.PLUGIN_VERSION, description: device.description ?? "" } }, width, height, templatesDir, config_1.BUNDLED_TEMPLATES_DIR);
|
|
346
346
|
}
|
|
347
347
|
const connectTimeoutMs = config.paintConnectTimeoutSeconds * 1000;
|
|
348
|
-
|
|
348
|
+
const gattBackend = config.useBleApi && app.bleApi ? (0, bleBackend_1.bleApiBackend)(app.bleApi, pluginVersion_1.PLUGIN_NAME) : undefined;
|
|
349
|
+
const paintWithRetries = () => (0, bleDiscovery_1.withRetries)(config.paintRetries, async (attempt) => {
|
|
349
350
|
if (attempt > 1) {
|
|
350
351
|
app.debug(`${label}: attempting paint ${attempt}/${config.paintRetries}`);
|
|
351
352
|
}
|
|
352
353
|
const startedAt = Date.now();
|
|
353
|
-
const gattBackend = config.useBleApi && app.bleApi ? (0, bleBackend_1.bleApiBackend)(app.bleApi, pluginVersion_1.PLUGIN_NAME) : undefined;
|
|
354
354
|
await driver.paint(bitmap, { address, aesKey: device.aesKey, connectTimeoutMs, reframe: device.reframe, gattBackend });
|
|
355
355
|
paintDurationMs = Date.now() - startedAt;
|
|
356
356
|
});
|
|
357
|
+
await (gattBackend ? (0, bleBackend_1.exclusiveBleManagerAccess)(paintWithRetries) : paintWithRetries());
|
|
357
358
|
(0, discoveredDevicesStore_1.touchDiscoveredDevice)(app, { address, vendor: target.vendor, pid: target.pid, hwVersion: target.hwVersion, metadata });
|
|
358
359
|
// Only a successful render counts as "repainted" for dedup/catch-up purposes - a failure must not be
|
|
359
360
|
// recorded here (see this function's doc comment on why).
|
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-beta5",
|
|
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",
|