@rhizomatics/signalk-einklabel-plugin 1.3.0-beta16 → 1.3.0-beta17

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/dist/config.d.ts CHANGED
@@ -69,6 +69,8 @@ export interface AdvancedDeviceSettings {
69
69
  * format. See `CompressionFormat`.
70
70
  */
71
71
  compressionFormat?: CompressionFormat;
72
+ /** Send the image without waiting for each write to be acknowledged (zhsunyco) - see `VendorDeviceConfig.writeWithoutResponse`. Unset means off. */
73
+ writeWithoutResponse?: boolean;
72
74
  /** One-shot override to repaint even if the data is unchanged; cleared automatically once that repaint completes. */
73
75
  forceRepaint?: boolean;
74
76
  /** Per-device override; if omitted, the vendor driver may fall back to a stock/manufacturer-default key. */
package/dist/config.js CHANGED
@@ -530,6 +530,13 @@ function configSchema(app, discovered = []) {
530
530
  description: `Chunked needs Compress upload on. Switch back to Auto if the label stops updating. ${docsLink("templates/#other-image-options")}`,
531
531
  default: "auto",
532
532
  }),
533
+ writeWithoutResponse: {
534
+ type: "boolean",
535
+ title: "Send image without waiting for each write (Zhsunyco)",
536
+ description: "Turn on if a Zhsunyco label fails with ATT error 0x0e when using the SignalK BLE Manager - its writes that " +
537
+ `wait for acknowledgement are rejected by these labels (SignalK 2.33 and earlier). ${docsLink("bluetooth/#zhsunyco-labels-and-the-ble-manager")}`,
538
+ default: false,
539
+ },
533
540
  forceRepaint: {
534
541
  type: "boolean",
535
542
  title: "Force repaint",
@@ -628,6 +635,7 @@ function configUiSchema() {
628
635
  compress: MARKDOWN,
629
636
  mirror: { "ui:widget": "radio", ...MARKDOWN },
630
637
  compressionFormat: { "ui:widget": "radio", ...MARKDOWN },
638
+ writeWithoutResponse: MARKDOWN,
631
639
  aesKey: { "ui:placeholder": "e.g. 00112233445566778899aabbccddeeff" },
632
640
  },
633
641
  },
@@ -83,6 +83,12 @@ export interface VendorDeviceConfig {
83
83
  compress?: boolean;
84
84
  /** Overrides the model's own wire format - see `CompressionFormat`. Defaults to `"auto"`. */
85
85
  compressionFormat?: CompressionFormat;
86
+ /**
87
+ * Send the image without waiting for each write to be acknowledged (zhsunyco; ignored otherwise).
88
+ * Works around SignalK's BLE Manager (2.33 and earlier) sending every acknowledged write as a
89
+ * "reliable" write, which zhsunyco labels reject with ATT error 0x0e. Defaults to `false`.
90
+ */
91
+ writeWithoutResponse?: boolean;
86
92
  /**
87
93
  * Receives one line per paint step (connecting, connected, uploading, ...), so a paint that stalls
88
94
  * shows in the log where it stopped. Omitted means no step logging.
@@ -13,6 +13,8 @@ const protocol_1 = require("./protocol");
13
13
  /** node-ble has no MTU API; this matches the reference driver's mtu(247)-9 default. */
14
14
  const UPLOAD_CHUNK_SIZE = 238;
15
15
  const CHUNK_WRITE_DELAY_MS = 20;
16
+ /** With nothing acknowledging each write, pace them further apart so the label isn't sent data faster than it can take it. */
17
+ const UNACKNOWLEDGED_CHUNK_WRITE_DELAY_MS = 50;
16
18
  const AUTH_SETTLE_DELAY_MS = 500;
17
19
  const STATUS_WAIT_TIMEOUT_MS = 60000;
18
20
  /** Bounds `readDeviceDetails`' whole connect+read attempt during a scan - see its doc comment. */
@@ -89,14 +91,15 @@ class ZhsunycoDriver {
89
91
  // Upload offsets and the refresh length both count bytes of whatever's actually sent - the
90
92
  // compressed payload when compressing, not the raw buffer it inflates back to.
91
93
  const payload = compress ? (0, compression_1.compressWolinkBlocks)(pixelData) : pixelData;
94
+ const acknowledged = !config.writeWithoutResponse;
92
95
  log(`uploading ${payload.length} bytes${compress ? ` (compressed from ${pixelData.length})` : ""} in ` +
93
- `${Math.ceil(payload.length / UPLOAD_CHUNK_SIZE)} writes`);
96
+ `${Math.ceil(payload.length / UPLOAD_CHUNK_SIZE)} ${acknowledged ? "acknowledged" : "unacknowledged"} writes`);
94
97
  for (let offset = 0; offset < payload.length; offset += UPLOAD_CHUNK_SIZE) {
95
98
  const chunk = payload.subarray(offset, offset + UPLOAD_CHUNK_SIZE);
96
- await conn.write(protocol_1.WOLINK_SERVICE_UUID, protocol_1.WOLINK_CHARACTERISTIC_UUIDS.data, Buffer.concat([(0, protocol_1.commandHeader)(protocol_1.COMMAND.uploadBlock, offset), chunk]), true);
97
- await (0, bleDiscovery_1.sleep)(CHUNK_WRITE_DELAY_MS);
99
+ await conn.write(protocol_1.WOLINK_SERVICE_UUID, protocol_1.WOLINK_CHARACTERISTIC_UUIDS.data, Buffer.concat([(0, protocol_1.commandHeader)(protocol_1.COMMAND.uploadBlock, offset), chunk]), acknowledged);
100
+ await (0, bleDiscovery_1.sleep)(acknowledged ? CHUNK_WRITE_DELAY_MS : UNACKNOWLEDGED_CHUNK_WRITE_DELAY_MS);
98
101
  }
99
- await conn.write(protocol_1.WOLINK_SERVICE_UUID, protocol_1.WOLINK_CHARACTERISTIC_UUIDS.data, (0, protocol_1.commandHeader)(compress ? protocol_1.COMMAND.refreshCompressed : protocol_1.COMMAND.refreshUncompressed, payload.length), true);
102
+ await conn.write(protocol_1.WOLINK_SERVICE_UUID, protocol_1.WOLINK_CHARACTERISTIC_UUIDS.data, (0, protocol_1.commandHeader)(compress ? protocol_1.COMMAND.refreshCompressed : protocol_1.COMMAND.refreshUncompressed, payload.length), acknowledged);
100
103
  log("refresh sent, waiting for the label to finish");
101
104
  // `Promise.race` can't cancel its loser, so once `statusReceived` settles (the common case)
102
105
  // this timer would otherwise sit alive for the rest of its 60s regardless - see the identical
@@ -375,6 +375,7 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
375
375
  mirror: device.advanced?.mirror,
376
376
  compress: device.advanced?.compress,
377
377
  compressionFormat: device.advanced?.compressionFormat,
378
+ writeWithoutResponse: device.advanced?.writeWithoutResponse,
378
379
  gattBackend,
379
380
  log: (message) => app.debug(`${label}: ${message}`),
380
381
  });
package/docs/bluetooth.md CHANGED
@@ -50,6 +50,25 @@ Check for the `bluetooth` service working at both host level and inside the Sign
50
50
 
51
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.
52
52
 
53
+ ## Zhsunyco Labels and the BLE Manager
54
+
55
+ With the "Use the SignalK BLE Manager API" setting on, Zhsunyco labels can connect and authenticate, then fail as soon as the image upload starts, with `Operation failed with ATT error: 0x0e` in the log. SignalK's BLE Manager (2.33 and earlier) sends every write that waits for an acknowledgement as a Bluetooth "reliable" write, which these labels don't support.
56
+
57
+ Either:
58
+
59
+ - turn on _Send image without waiting for each write (Zhsunyco)_ in the label's _Advanced settings_ - the image is sent with a small gap between writes instead, and the label still confirms once the whole image has arrived; or
60
+ - switch the BLE Manager setting off, if no other plugins need to share Bluetooth with this one.
61
+
62
+ Gicisky labels aren't affected, since they don't use acknowledged writes.
63
+
64
+ ## Stuck Labels
65
+
66
+ A label can itself get into a stuck state - usually after several connections were cut off part way through a repaint - where it still advertises and accepts connections, but no longer lists its services. Every repaint then connects and fails a couple of seconds later, with `Service not available` in this plugin's log, or `Characteristic … was not found` from other software such as Home Assistant.
67
+
68
+ You can confirm it's the label rather than your server: `bluetoothctl info <label address>` shows no `UUID:` lines even after a connection, and the same failure happens from a different adapter or computer.
69
+
70
+ The fix is to power-cycle the label - take the battery out for 10-20 seconds, put it back, then trigger a repaint (for example with _Force repaint_). If it still fails, the manufacturer's own app may be needed to reset it.
71
+
53
72
  ## Tuning Bluetooth Connections
54
73
 
55
74
  Labels spend most of their time asleep, so they can be slow to accept a connection, and slow to answer while an image is being sent to them. Linux's Bluetooth defaults are set with phones, headphones and sensors in mind, so if connections to labels regularly time out, or drop partway through a repaint (for example with GATT or connection-abort errors in the log), some Bluetooth settings on the server may need adjusting:
package/docs/faq.md CHANGED
@@ -35,6 +35,10 @@ Check if the text boxes are normal text or flowed text, and correct to normal te
35
35
 
36
36
  That's the bundled fallback warning, not necessarily an error in this plugin - it means the most recent repaint failed, whatever produced the content (a broken hand-authored template, or a `TemplateProvider` extension like [`@rhizomatics/signalk-einklabel-genai-plugin`](templates.md#genai-rendering) - e.g. its LLM call failing on no network/API access, an invalid API key, or a response that wasn't a renderable SVG, after using up its configured retries). Check the SignalK server logs (debug logging on for this plugin) for the specific error, and if it's a GenAI device, check that plugin's own provider/API key/model settings. This plugin deliberately never leaves old content on screen when a repaint fails - it retries automatically at the next scheduled interval.
37
37
 
38
+ ## Repaints fail with "Service not available"
39
+
40
+ If a label connects but every repaint fails a couple of seconds later with `Service not available`, the label itself has most likely got stuck - take its battery out for 10-20 seconds and try again. See [Stuck Labels](bluetooth.md#stuck-labels).
41
+
38
42
  ## Bluetooth problems
39
43
 
40
44
  Choosing an adapter, adapters that stop responding, and Bluetooth starting after SignalK are covered on the [Bluetooth](bluetooth.md) page.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-einklabel-plugin",
3
- "version": "1.3.0-beta16",
3
+ "version": "1.3.0-beta17",
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",