@rhizomatics/signalk-einklabel-plugin 0.9.0 → 0.9.1

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 CHANGED
@@ -1,20 +1,33 @@
1
+ # 0.9.1
2
+
3
+ - Fix for upstream EventEmitter leak in `@naugehyde/node-ble`
4
+
1
5
  # 0.9.0
2
6
 
3
7
  ## Tide Clock Example
8
+
4
9
  - Rename `third_quarter` to `last_quarter` for moon phase icons in `templates/.assets/lunar_phases` to match Derived Data plugin
5
10
  - `tide.svg` renamed to `tides\416x240-BWRY.svg`. Original maintained but labelled as deprecated
6
- - Added a simpler *250x128* version of tide clock for 2.13" labels
11
+ - Added a simpler _250x128_ version of tide clock for 2.13" labels
12
+
7
13
  ## Fixes
14
+
8
15
  - Fix configuration JSON holding older versions of itself as subentries
9
- and data changes
16
+ and data changes
10
17
  - Fix most cases of scanned devices not appearing in device dropdown choice
18
+
11
19
  ## Improvements
12
- - Repaint state used to track which labels to repaint separately tracks the template
20
+
21
+ - Repaint state used to track which labels to repaint separately tracks the template
13
22
  - `nearestColour` algorithm in Zhsunyco driver supports ESLs that only have BWR or BW
23
+
14
24
  ## Device Selection
25
+
15
26
  - New 'ALL' as device option, and by default disable initial scan, to optimize support for single devices, so can configure and go without waiting for scan
16
27
  - Scan and explicit device selection only required for boats with multiple ESL devices
28
+
17
29
  ## Templating
30
+
18
31
  - Now support a directory of templates, where each is named like `416-240-BWRY.svg` to support same functions on different devices.
19
32
  - Picks the template within the directory that most closely matches the tide clock height/width/colour-set, if not matched then height/width, and then best h/w ratio for nearest width
20
33
  - `template/assets` is now `template/.assets`
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  Fully working but limited vendor/product support and requires Linux for device access.
11
11
 
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.
12
+ A SignalK plugin to display data from SignalK paths, Resource APIs and plugins on Electronic Shelf Labels (ESL) over a Bluetooth Low Energy (BLE) connection using simple SVG templates.
13
13
 
14
14
  ## What is an ESL?
15
15
 
@@ -23,18 +23,18 @@ Being battery operated, they can be stuck on anywhere without wiring - the only
23
23
 
24
24
  Unlike some eInk projects, this plugin doesn't require any physical modification to the labels, or loading any new firmware. It can send an image to a supported shelf label fresh out of the box.
25
25
 
26
- Most of these requirements are about making SignalK work with Bluetooth Low Energy, which is good thing to have anyway, since vendors like Victron, Switchbot, Ruuvi and others have BLE enabled hardware that's useful to have on a boat. [Direct BLE support](https://github.com/SignalK/signalk-server/issues/2411) in SignalK is being planned in 2026.
26
+ Most of requirements below are to make SignalK work with Bluetooth Low Energy, which is good thing to have anyway, since vendors like Victron, Switchbot, Ruuvi and others have BLE enabled hardware that's useful to have on a boat. [Direct BLE support](https://github.com/SignalK/signalk-server/issues/2411) in SignalK is being planned in 2026 and this plugin will support that when it comes.
27
27
 
28
28
  1. A SignalK server, **running Linux**
29
29
 
30
30
  - MacOS and Windows aren't supported by the [BLE interface layer](https://www.npmjs.com/package/@naugehyde/node-ble), however can be used for template development and
31
31
  debugging (everything except `scan` and `paint`)
32
32
 
33
- 2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy), which is Bluetooth v4.0 or higher
33
+ 2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy).
34
34
 
35
35
  - Bluetooth adapters for Linux can be tricky, TP-Link UB400 and Asus USB-BT500 are two well-known and available ones
36
36
  - Some Raspberry Pi models come with suitable Bluetooth it built-in
37
- - Don't worry about the very latest Bluetooth versions, 4.0 is basic, 5.0 is nice
37
+ - Don't worry about the very latest Bluetooth versions, 4.0 is minimum for BLE, 5.0 is nice
38
38
  - Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
39
39
 
40
40
  3. `bluez` package installed in Linux
@@ -44,18 +44,18 @@ Most of these requirements are about making SignalK work with Bluetooth Low Ener
44
44
 
45
45
  4. One or more supported Electronic Shelf Labels
46
46
 
47
- - The label used for testing this is the [ZhunyCo 3.7 BRWY](https://www.aliexpress.com/item/1005010050104435.html)
47
+ - The label used for testing this is the [ZhunyCo 3.7" BRWY](https://www.aliexpress.com/item/1005010050104435.html)
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
- 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.
54
+ Once you have all of that, it may be worth also installing [signalk-victron-ble](https://github.com/stefanor/signalk-victron-ble),[signalk-ruuvitag-plugin](https://github.com/vokkim/signalk-ruuvitag-plugin) or [bt-sensors-plugin](https://github.com/naugehyde/bt-sensors-plugin-sk) to pull in data from other sensors and equipment.
55
55
 
56
56
  ## Installation
57
57
 
58
- Look for **eInk Label Instrument** in the [SignalK AppStore]() on your
58
+ Look for **eInk Label Displays** in the **SignalK AppStore** on your
59
59
  server ( under _Apps & Plugins_ on the latest version).
60
60
 
61
61
  ### Using Outside of SignalK
@@ -66,6 +66,12 @@ The plugin can also be installed as a stand-alone module, which can be useful fo
66
66
  npm install @rhizomatics/signalk-einklabel-plugin
67
67
  ```
68
68
 
69
+ ## Configuration
70
+
71
+ Use the standard configuration option in the SignalK menu for the plugin.
72
+
73
+ ![Plugin Configuration](docs/assets/screenshots/plugin_config.png)
74
+
69
75
  ## Examples
70
76
 
71
77
  ### Tide Clock
@@ -76,17 +82,37 @@ Template available as 416x240-BWRY for 3.7" ESLs and a simpler template, sized 2
76
82
 
77
83
  #### Pre-requisites
78
84
 
79
- * [signalk-tides](https://github.com/openwatersio/signalk-tides) plugin to be installed and publishing tides to the Resources API.
80
- - The [tides](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/templates/tides/) templates can be customized to run with other APIs or take data only from SignalK data paths.
81
- - In the template it uses paths like `source=resources,resource=tides,provider=tides,path=extremes[0].time,format=local_time` to get the first tide time, ensures its the preferred `signalk-tides` provider and makes it a simple local time rather than a UTC date time.
82
- * To show the lunar phase, the `environment.moon.phaseName` path is required, which can
85
+ A _tides_ provider plugin for the Resources API installed and enabled, currently one of:
86
+
87
+ - [signalk-tides](https://github.com/openwatersio/signalk-tides) - uses [neaps](https://github.com/openwatersio/neaps) library for international off-line coverage
88
+ - [signalk-mareas-ihm](https://github.com/Aitonos/signalk-mareas-ihm) - interfaces with official Spanish IHM tidal predictions, or falls back to Open Meteo and _signalk-tides_
89
+
90
+ The [tides](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/templates/tides/) templates can be customized to run with any tide provider, a specific one, or switch to other APIs or SignalK data paths.
91
+
92
+ - In the template it uses a SVG description like `source=resources,resource=tides,provider=tides,path=extremes[0].time,format=local_time` to get the first tide time, ensures its the preferred `signalk-tides` provider and makes it a simple local time rather than a UTC date time.
93
+
94
+ To show the lunar phase, the `environment.moon.phaseName` path is required, which can
83
95
  be easily achieved by installing and configuring the `derived-data` plugin.
84
96
 
85
- ## Configuration
97
+ ## Setting up a Label
86
98
 
87
- Use the standard configuration option in the SignalK menu for the plugin.
99
+ Enable the plugin, and use the large **+** sign to add a label, which open up these fields.
88
100
 
89
- ![Plugin Configuration](docs/assets/screenshots/plugin_config.png)
101
+ ![Label Config](docs/assets/screenshots/label_config.png)
102
+
103
+ - _Friendly Name_ - Give the label any name (word or phrase) you like, for example 'Tide Clock'
104
+ - _Device_ - Unless you have multiple labels, don't bother with pre-scanning or selecting a specifig device, instead pick **"All discovered devices"** and it will paint any compatible labels it finds. If you want to pick a specific device, you'll need to wait for a device scan to complete.
105
+ - _Template_ - Choose a built in template, or one you've added to the local templates directory
106
+ - _Repaint Trigger_- Do you want this to repaint every few hours (at a chosen minutes past hour), or when a SignalK path changes?
107
+ - If its a SignalK path, enter it next, for example `environment.tide.state`
108
+ - If its time based, enter how many hours between repaints, for example 00:00/08:00/16:00 for an 8h schedule, and if you want a specific number of minutes after the hour.
109
+
110
+ There are also two more advanced options, which can usually be ignored.
111
+
112
+ - _BLE AES key_ - Only needed if the default key doesn't work and you have a better alternative, otherwise ignore
113
+ - _Force Repaint_ - Next time the label is due to be painted, update even if the data or template hasn't changed (this flag will automatically be cleared after this.)
114
+
115
+ When the plugin starts, it will automatically re-paint the label if its new, or the last timed slot was missed and the data has changed.
90
116
 
91
117
  ### Scanning for Devices
92
118
 
@@ -96,21 +122,18 @@ One other quirk is that some devices respond with a different name at different
96
122
 
97
123
  The plugin can optionally re-scan whenever it starts up (off by default), although this isn't essential once a label has been configured. Devices found by any scan are remembered across restarts - see the FAQ below.
98
124
 
99
- #### Selecting a Device
100
-
101
- A device's "Device" field can either be a specific device picked from the scan dropdown, or **"All discovered devices"**, which paints that same template/trigger to every device the plugin currently knows about. This is the simplest option for a boat with just one label - there's no need to scan first and pick it out, and if nothing's been discovered yet, selecting it triggers a scan itself the first time it's needed. It also covers several identical labels with one config entry, without listing each one out.
102
-
103
- ### Scheduling
104
-
105
- There are two ways of scheduling scans:
125
+ ## Vendors
106
126
 
107
- #### Time Based
127
+ ### Zhsunyco
108
128
 
109
- 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.
129
+ Also known as 'Suny' and 'WOLink'.
110
130
 
111
- #### Path Subscription
131
+ - [BLE ESLs](https://www.zhsunyco.com/digital-display-solution-for-small-retail-business/ble-esl-solution/)
132
+ - The range of labels available on retail sites like AliExpress may be larger than on their corporate site
133
+ - In mid 2026, a 4 colour (BWRY) 3.7" label retailed for about $35, with quantity discounts for bulk sets
134
+ - Cheapest units are 2 colour 1.54", and they go up to 7.5"
112
135
 
113
- The devices will be painted when the plugin starts, and then every time the selected SignalK path changes.
136
+ Python code for a variety of their labels at https://github.com/roxburghm/zhsunyco-esl and https://github.com/NickWaterton/Wolink
114
137
 
115
138
  ## Templating
116
139
 
@@ -118,7 +141,9 @@ Templates are simply SVG files, to which expressions can be added to use SignalK
118
141
 
119
142
  ### Template Families (multiple panel sizes/colours)
120
143
 
121
- A "Template" selection can either be one specific `.svg` file, or a _directory_ holding several same-purpose templates for different panel sizes/colour-sets, e.g. `templates/tides/416x240-BWRY.svg` and `templates/tides/250x128-BWRY.svg` both implement the tide clock, just at different sizes. Each file is named `<width>x<height>-<colours>.svg`, where `<colours>` is one letter per supported colour: `B`(lack)/`W`(hite)/`R`(ed)/`Y`(ellow) - e.g. `BWRY` for a 4-colour panel, `BWR` for a 3-colour one.
144
+ A "Template" selection can either be one specific `.svg` file, or a _directory_ holding several versions of the same template for different panel sizes/colour-sets, e.g. `templates/tides/416x240-BWRY.svg` and `templates/tides/250x128-BWRY.svg` both implement the tide clock, just at different sizes.
145
+
146
+ Each template is named `<width>x<height>-<colours>.svg`, where `<colours>` is one letter per supported colour: `B`(lack)/`W`(hite)/`R`(ed)/`Y`(ellow) - e.g. `BWRY` for a 4-colour panel, `BWR` for a 3-colour one.
122
147
 
123
148
  Selecting the directory (e.g. `tides`) instead of one file lets one `DeviceConfig` entry - especially a `device: "All discovered devices"` entry covering several different physical panels - automatically pick the best-fitting file for each device's actual size/colours, trying in order:
124
149
 
@@ -140,7 +165,7 @@ The source can be overridden to use the SignalK server's Resources API instead.
140
165
 
141
166
  For example, `source=resources,resource=tides,provider=tides,path=station.name` picks the `tides` resource and pulls the `station.name` path out of the JSON response - this works for any resource type (`tides`, `waypoints`, `routes`, ...), and needs nothing configured: the plugin reaches the Resources API directly. Where a resource is specified, it will be fetched once for that render, and subsequent fields sourced from the same resource use that cached response. `provider` is optional, the default provider will be used if not specified.
142
167
 
143
- #### Plugin Data
168
+ #### Plugin Derived Data
144
169
 
145
170
  `source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Available paths:
146
171
 
@@ -163,7 +188,7 @@ SignalK's unit preferences are used to automatically convert a `signalk`-sourced
163
188
 
164
189
  Note that for dates and times, the server timezone must be set correctly, for example `Europe/London` rather than the default `Etc/UTC`. This can be done on Linux using `timedatectl` or `raspi-config` if using a Raspberry Pi with Raspian.
165
190
 
166
- Common categories:
191
+ ##### Common categories
167
192
 
168
193
  - `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
169
194
  - `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
@@ -183,7 +208,7 @@ path=environment.moon.phaseName,assets=lunar_phases
183
208
 
184
209
  which resolves against `templates/.assets/lunar_phases/` (bundled, or your own configured `templates` directory's `.assets/lunar_phases/` if you have one). The resolved value (e.g. `"Waning Gibbous"`, as published by the [derived-data](https://www.npmjs.com/package/signalk-derived-data) plugin) is normalized to match a filename - lower-cased, punctuation and spaces collapsed to underscores - so `"Waning Gibbous"` picks `waning_gibbous.svg` out of that directory. If the underlying path has no value at all (e.g. the `derived-data` plugin isn't installed), or the value doesn't normalize to any file in the directory, the `<image>` element is omitted from that render - no broken image, no placeholder, nothing shown - and a line is logged to the console so a missing/unmatched value isn't silently invisible.
185
210
 
186
- If you don't like the bundled moon phase icons, save your own `<value>.svg` files in the `.assets/lunar_phases` sub-directory of your configured `templates` directory - the whole directory is used in place of the bundled one, so add all 8 phases you want to keep, not just the ones you're changing.
211
+ If you don't like the bundled moon phase icons, save your own `<value>.svg` files in the `.assets/lunar_phases` sub-directory of your configured `templates` directory - the whole directory is used in place of the bundled one, so add all 8 phases you want to keep, not just the ones you're changing. These moon phases can be re-used in any other label.
187
212
 
188
213
  This is a general mechanism, not specific to moon phases - any `source`/`context`/`path`/`format` combination valid for a `<text>` binding works here too (a `source=resources` value, an explicit `category=`, etc.), the only difference is the required `assets=` directory and the "no match -> no image" behaviour instead of substituted text. To add your own, put a directory of `<value>.svg` files under an `.assets/<name>` sub-directory of your `templates` directory, add an `<image>` element in your SVG editor at the size/position you want, and give it a `<desc>` the same way you would a text field - overriding just the template, just its assets, or both together, all work independently.
189
214
 
@@ -195,19 +220,6 @@ Three font types are loaded by default, use the generic font family, or exact fo
195
220
  - `sans-serif` - `Roboto`
196
221
  - `monospace` - `Roboto Mono`
197
222
 
198
- ## Vendors
199
-
200
- ### Zhsunyco
201
-
202
- Also known as 'Suny' and 'WOLink'.
203
-
204
- - [BLE ESLs](https://www.zhsunyco.com/digital-display-solution-for-small-retail-business/ble-esl-solution/)
205
- - The range of labels available on retail sites like AliExpress may be larger than on their corporate site
206
- - In mid 2026, a 4 colour (BWRY) 3.7" label retailed for about $35, with quantity discounts for bulk sets
207
- - Cheapest units are 2 colour 1.54", and they go up to 7.5"
208
-
209
- Python code for a variety of their labels at https://github.com/roxburghm/zhsunyco-esl and https://github.com/NickWaterton/Wolink
210
-
211
223
  ## Architecture
212
224
 
213
225
  The primary things managed and provided by the plugin are:
@@ -271,7 +283,9 @@ Placeholder text isn't necessary, and is ignored by the plugin, but makes it muc
271
283
 
272
284
  Inkscape adds its own metadata to images, which can be stripped off by exporting a simple SVG, although can be left in place with no harm; main reason to simplify the SVG is manual changes in a text editor.
273
285
 
274
- 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.
286
+ 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`.
287
+
288
+ 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.
275
289
 
276
290
  ### Debugging Templates
277
291
 
@@ -102,6 +102,20 @@ async function withDiscovery(durationMs, fn) {
102
102
  destroy();
103
103
  }
104
104
  }
105
+ /**
106
+ * node-ble's `Device#connect()` adds a fresh D-Bus `PropertiesChanged` listener on every call
107
+ * and only removes it via a *successful* `Device#disconnect()` - a connect that times out or
108
+ * rejects (exactly the case this function exists to bound, for a device that's out of range or
109
+ * stuck mid-handshake) never reaches that cleanup, so each failed attempt leaks one listener on
110
+ * the underlying Device object. A flaky device polled on every scan/repaint cycle eventually
111
+ * trips Node's MaxListenersExceededWarning. Reach into node-ble's internal BusHelper (not part
112
+ * of its public API, but the only thing that actually holds the listener) and clear it directly
113
+ * regardless of how the attempt ended, so at most one listener is ever outstanding.
114
+ * (github.com/naugehyde/node-ble — fix proposed upstream, not yet released.)
115
+ */
116
+ function clearStaleConnectListener(device) {
117
+ device.helper?.removeAllListeners?.("PropertiesChanged");
118
+ }
105
119
  /**
106
120
  * `device.connect()` has no timeout of its own - BlueZ's underlying D-Bus `Connect` call can hang
107
121
  * indefinitely for a device that's out of range or stuck mid-handshake, which would otherwise
@@ -112,7 +126,12 @@ async function withDiscovery(durationMs, fn) {
112
126
  async function connectWithTimeout(device, timeoutMs) {
113
127
  const connecting = device.connect();
114
128
  let timedOut = false;
115
- await Promise.race([connecting, sleep(timeoutMs).then(() => void (timedOut = true))]);
129
+ try {
130
+ await Promise.race([connecting, sleep(timeoutMs).then(() => void (timedOut = true))]);
131
+ }
132
+ finally {
133
+ clearStaleConnectListener(device);
134
+ }
116
135
  if (timedOut) {
117
136
  connecting.then(() => device.disconnect()).catch(() => { });
118
137
  throw new Error(`connecting to device timed out after ${timeoutMs}ms`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-einklabel-plugin",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Display SignalK data on eInk Electronic Shelf Labels",
5
5
  "keywords": [
6
6
  "ble",