@rhizomatics/signalk-einklabel-plugin 1.3.0-beta8 → 1.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +21 -511
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +26 -1
  5. package/dist/config.d.ts +62 -15
  6. package/dist/config.js +252 -60
  7. package/dist/devices/bleBackend.js +44 -7
  8. package/dist/devices/bleDiscovery.d.ts +5 -0
  9. package/dist/devices/bleDiscovery.js +18 -0
  10. package/dist/devices/gicisky/compression.d.ts +22 -9
  11. package/dist/devices/gicisky/compression.js +128 -13
  12. package/dist/devices/gicisky/encode.d.ts +1 -1
  13. package/dist/devices/gicisky/encode.js +2 -2
  14. package/dist/devices/gicisky/index.js +9 -3
  15. package/dist/devices/gicisky/layout.d.ts +13 -1
  16. package/dist/devices/gicisky/layout.js +21 -0
  17. package/dist/devices/types.d.ts +26 -0
  18. package/dist/devices/types.js +2 -0
  19. package/dist/devices/zhsunyco/compression.d.ts +1 -0
  20. package/dist/devices/zhsunyco/compression.js +30 -0
  21. package/dist/devices/zhsunyco/index.js +44 -9
  22. package/dist/devices/zhsunyco/protocol.d.ts +2 -0
  23. package/dist/devices/zhsunyco/protocol.js +2 -0
  24. package/dist/docs/templateReference.d.ts +18 -0
  25. package/dist/docs/templateReference.js +207 -0
  26. package/dist/email/emailSender.d.ts +27 -0
  27. package/dist/email/emailSender.js +66 -0
  28. package/dist/plugin.js +2 -2
  29. package/dist/render/fieldsTable.d.ts +15 -0
  30. package/dist/render/fieldsTable.js +52 -0
  31. package/dist/render/llmPrompt.d.ts +86 -0
  32. package/dist/render/llmPrompt.js +199 -0
  33. package/dist/render/mirror.d.ts +11 -0
  34. package/dist/render/mirror.js +24 -0
  35. package/dist/repaintScheduler.d.ts +8 -0
  36. package/dist/repaintScheduler.js +86 -29
  37. package/dist/resolveApiUrl.js +3 -0
  38. package/docs/bluetooth.md +162 -0
  39. package/docs/cli.md +131 -0
  40. package/docs/examples/README.md +25 -0
  41. package/docs/examples/tide-clock.md +93 -0
  42. package/docs/examples/watch-schedule.md +37 -0
  43. package/docs/extending.md +33 -0
  44. package/docs/faq.md +44 -0
  45. package/docs/getting-started.md +111 -0
  46. package/docs/templates.md +142 -0
  47. package/package.json +16 -7
package/CHANGELOG.md CHANGED
@@ -1,6 +1,41 @@
1
1
  # 1.3.0
2
2
 
3
- First implementation of using new SignalK BLE Manager rather than directly using the `bluez` services. Off by default until longer term stability demonstrated.
3
+ ## BLE Manager
4
+
5
+ First implementation of using new SignalK BLE Manager rather than directly using the `bluez` services.
6
+
7
+ - Off by default until longer term stability demonstrated.
8
+ - It may be less reliable for some devices that can be reached with direct bluez access - try it and fall back to the direct mode if so.
9
+ - Issues with Zhsunyco are detailed in the [Bluetooth](https://signalk-einklabel.rhizomatics.org.uk/bluetooth/) docs, with a list of the fixes awaited upstream in SignalK. A Gicisky label has worked with it.
10
+ - BLE Manager use reduces interference between plugins competing for same BLE devices
11
+ - Recommend using SignalK at least release v2.33.0.
12
+
13
+ ## Compression
14
+
15
+ Images are now compressed before sending - no change to what's displayed, but send time is massively reduced for labels with lots of empty space, so less battery is used and sends are less likely to fail.
16
+
17
+ - On by default for Zhsunyco labels and Gicisky 7.5"/10.2" labels; can be turned off per label, or with `--no-compress` on the CLI
18
+ - Experimental opt-in _Wire format_ `chunked` sends a Gicisky 4.2" BWR compressed too (`--compression-format chunked` on the CLI)
19
+
20
+ ## Templates
21
+
22
+ - Simplified use of `label` and `einklabel` as a source of template fields, now only `label` needed, though `einklabel` won't break existing templates
23
+
24
+ ## Advanced Options
25
+
26
+ Connect timeout and retries can now be defined per label.
27
+
28
+ SignalK Base URL now accepts a free-text value
29
+
30
+ Some labels may need images flipped, so a per-label _Mirror_ option can flip the image horizontally, vertically, or both (rotate 180°, for a label mounted upside down). Also available as `--mirror` on the CLI `paint` and `render` commands.
31
+
32
+ Different 'chunking' options to help with new label models.
33
+
34
+ All the advanced options now grouped together to make the config clearer. Note that if you roll back to a previous version you may have to re-enter these values (they are automatically moved to the new section when upgrading).
35
+
36
+ ## Dependencies
37
+
38
+ - `xmldom` updated to fix security issue with markup injection
4
39
 
5
40
  # 1.2.3
6
41
 
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/LICENSE)
9
9
  [![boat tech directory](https://boat-tech-directory.rhizomatics.org.uk/images/badge.svg)](https://boat-tech-directory.rhizomatics.org.uk)
10
10
 
11
- 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, or optionally created from a crafted prompt by GenAI if the companion [`@rhizomatics/signalk-einklabel-genai-plugin`](https://github.com/rhizomatics/signalk-einklabel-genai-plugin) is installed. Supports ESLs from two of the major Chinese manufacturers, and requires no firmware or hardware modifications, switch on and go.
11
+ 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, or optionally created from a crafted prompt by GenAI if the companion [`@rhizomatics/signalk-einklabel-genai-plugin`](https://github.com/rhizomatics/signalk-einklabel-genai-plugin) is installed. Supports ESLs from two of the major Chinese manufacturers, and requires **no firmware or hardware modifications**, switch on and go.
12
12
 
13
13
  ![Companionway Tidal Clock](docs/assets/images/real_tidal_clock.jpg)
14
14
 
@@ -20,523 +20,28 @@ Since they are designed to be used in large quantity in small shops, they are ch
20
20
 
21
21
  Being battery operated, they can be stuck on anywhere without wiring - the only location constraints are bluetooth range, visibility (they need ambient light since the display is more like paper than a traditional lit-up electronic display) and, for some labels, being out of the weather if they are not waterproof, although IP65 labels are available.
22
22
 
23
- ## Pre-requisites
23
+ ## Quick Start
24
24
 
25
- 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
+ 1. Check the [pre-requisites](https://signalk-einklabel.rhizomatics.org.uk/getting-started/#pre-requisites) - mainly a Linux SignalK server with a Bluetooth Low Energy adapter, or the SignalK BLE Manager API
26
+ 2. Install **eInk Label Displays** from the **SignalK AppStore** ( under _Apps & Plugins_ )
27
+ 3. In the plugin config, add a label, choose **"All discovered devices"** and a template such as the [Tide Clock](https://signalk-einklabel.rhizomatics.org.uk/examples/tide-clock/)
26
28
 
27
- 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.
29
+ ## Documentation
28
30
 
29
- This plugin can reach BLE hardware two ways - pick whichever fits your setup:
31
+ Full documentation is at [signalk-einklabel.rhizomatics.org.uk](https://signalk-einklabel.rhizomatics.org.uk):
30
32
 
31
- - **Direct BlueZ access** (default) - the plugin talks to BlueZ over D-Bus itself. Requirements 1-3 below apply.
32
- - **SignalK BLE Manager API** (opt-in) - SignalK server >= 2.32.0 ships a [BLE Provider/Consumer API](https://github.com/SignalK/signalk-server/issues/2411) (admin UI: "BLE Manager") that arbitrates adapter access across every BLE-consuming plugin instead of each one grabbing `hci0` for itself, and can source BLE over a remote gateway instead of local hardware at all. Enable the "Use the SignalK BLE Manager API" setting in this plugin's config once it's available (it only appears once the running server has it) - requirements 1-3 below then become the SignalK server's problem, under its own Bluetooth admin settings, not this plugin's.
33
-
34
- 1. A SignalK server, **running Linux** (direct BlueZ mode only - BLE Manager mode with a remote gateway provider has no such requirement)
35
-
36
- - MacOS and Windows aren't supported by the [BLE interface layer](https://www.npmjs.com/package/@naugehyde/node-ble) in direct BlueZ mode, however they can be used for template development and
37
- debugging (everything except `scan` and `paint`)
38
-
39
- 2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy) - direct BlueZ mode only; in BLE Manager mode this is whatever the server's own Bluetooth settings provide.
40
-
41
- - Bluetooth adapters for Linux can be tricky
42
- - TP-Link UB400 and Asus USB-BT500 are two well-known and available ones, though the ASUS USB-BT500 one can have problems with some Pi type boards
43
- - CSR4.0 dongles (CSR8510 chip) have had kernel support for years, and there are well known work arounds for some of them, including in the Linux kernel since v5.17
44
- - Some Raspberry Pi models come with suitable Bluetooth built in
45
- - See advice at [Recommended Bluetooth Adapters for Linux](https://github.com/morrownr/USB-WiFi/blob/main/home/Recommended_Bluetooth_Adapters_for_Linux.md)
46
- - Bluetooth adapters typically prefer being in USB2.0 ports rather than USB3.0 ports, since often the USB3.0 implementation leaks radio energy on the same 2.4Ghz spectrum as Bluetooth. If no USB2.0 port available, try a shielded USB2.0 extension lead to distance the dongle from the port. Some dongle manufacturees seems to do a better job at shielding for this than others.
47
-
48
- > - Don't worry about the very latest Bluetooth versions, 4.0 is minimum for BLE, 5.0 is nice
49
- > - Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
50
-
51
- 3. `bluez` package installed in Linux - direct BlueZ mode only
52
-
53
- - No need to do this if you have a Raspberry Pi with recent Raspian version, since bluez comes built in.
54
- - If you're not running a Raspberry Pi, then ensure that the `dbus` package is installed
55
-
56
- 4. One or more supported Electronic Shelf Labels
57
-
58
- - The labels used for testing this are the [Zhsunyco 3.7" BWRY](https://www.aliexpress.com/item/1005010050104435.html) and a [Gicisky 2.9" BWRY](https://www.aliexpress.com/item/1005012933325056.html)
59
-
60
- 5. Correct time zone set on server if local time is to be shown on display
61
-
62
- - See [FAQ](#faq-timezone)
63
- - If not set, everything will work, but you may see the wrong zone or not have daylight savings applied
64
-
65
- 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.
66
-
67
- ## Installation
68
-
69
- Look for **eInk Label Displays** in the **SignalK AppStore** on your
70
- server ( under _Apps & Plugins_ on the latest version).
71
-
72
- ### Using Outside of SignalK
73
-
74
- The plugin can also be installed as a stand-alone module, which can be useful for designing templates away from the boat, and makes available the `esl-cli` command line tool for scanning devices and debugging templates.
75
-
76
- ```bash
77
- npm install @rhizomatics/signalk-einklabel-plugin
78
- ```
79
-
80
- ## Configuration
81
-
82
- Use the standard configuration option in the SignalK menu for the plugin.
83
-
84
- ![Plugin Configuration](docs/assets/screenshots/plugin_config.png)
85
-
86
- ## Examples
87
-
88
- ### Tide Clock
89
-
90
- ![Tide Clock](docs/assets/screenshots/example_tidal_clock.png)
91
-
92
- Template available as 416x240-BWRY for 3.7" ESLs and a simpler template, sized 250x128, also BWRY, for the cheapest 2.13" labels.
93
-
94
- This mini tide clock is a 2.9" Gicisky device, less than £10 inc delivery in summer 2026.
95
-
96
- ![2.9" Tide Clock](docs/assets/images/mini_tidal_clock.png)
97
-
98
- #### Pre-requisites
99
-
100
- A _tides_ provider plugin for the Resources API installed and enabled, currently one of:
101
-
102
- - [signalk-tides](https://github.com/openwatersio/signalk-tides) - uses [neaps](https://github.com/openwatersio/neaps) library for international off-line coverage
103
- - [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_
104
-
105
- 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.
106
-
107
- - 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 it's the preferred `signalk-tides` provider and makes it a simple local time rather than a UTC date-time.
108
-
109
- To show the lunar phase, the `environment.moon.phaseName` path is required, which can
110
- be easily achieved by installing and configuring the `derived-data` plugin.
111
-
112
- ### Watch Schedule
113
-
114
- ![Watch Schedule](docs/assets/screenshots/example_watch_schedule.png)
115
-
116
- Template available as 416x240-BWRY for 3.7" ESLs.
117
-
118
- #### Pre-requisites
119
-
120
- - Source of `watch.current` and `watch.next` values
121
- - `signalk-watch-schedule` plugin
122
-
123
- ## Setting up a Label
124
-
125
- Enable the plugin, and use the large **+** sign to add a label, which opens up these fields.
126
-
127
- ![Label Config](docs/assets/screenshots/label_config.png)
128
-
129
- - _Friendly Name_ - Give the label any name (word or phrase) you like, for example 'Tide Clock'
130
- - _Device_ - Unless you have multiple labels, don't bother with pre-scanning or selecting a specific 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.
131
- - _Template_ - Choose a built-in template, one you've added to the local templates directory, or - if a companion plugin like [`@rhizomatics/signalk-einklabel-genai-plugin`](#genai-rendering) is installed - one of its own contributed entries (shown with a suffix, e.g. "forecast (GenAI)")
132
- - _Location/description_ - Optional free-text notes on where this label is physically mounted/viewed from, e.g. "chart table, viewed from ~1m in poor light" - available to any template as `source=einklabel,path=description` or `source=label,path=description`
133
- - _Repaint Trigger_- Do you want this to repaint every few hours (at a chosen minutes past hour), or when a SignalK path changes?
134
- - If it's a SignalK path, enter it next, for example `environment.tide.state`
135
- - If it's 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.
136
-
137
- There are also two more advanced options, which can usually be ignored.
138
-
139
- - _BLE AES key_ - Only needed if the default key doesn't work and you have a better alternative, otherwise ignore
140
- - _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.)
141
-
142
- When the plugin starts, it will automatically re-paint the label if it's new, or the last timed slot was missed and the data has changed.
143
-
144
- ### Scanning for Devices
145
-
146
- 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.
147
-
148
- One other quirk is that some devices respond with a different name at different times, for example the generic `WOESL` sometimes and model specific `WL17500C74` other times. However, the MAC address, e.g. `66:66:17:50:0D:2B` is constant, and this is what's tracked by the plugin.
149
-
150
- 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.
151
-
152
- ## Vendors
153
-
154
- ### Zhsunyco
155
-
156
- Also known as 'Suny' and 'WOLink'.
157
-
158
- - [BLE ESLs](https://www.zhsunyco.com/digital-display-solution-for-small-retail-business/ble-esl-solution/)
159
- - The range of labels available on retail sites like AliExpress may be larger than on their corporate site
160
- - In mid 2026, a 4 colour (BWRY) 3.7" label retailed for about $35, with quantity discounts for bulk sets
161
- - Cheapest units are 2 colour 1.54", and they go up to 7.5"
162
-
163
- Python code for a variety of their labels at https://github.com/roxburghm/zhsunyco-esl and https://github.com/NickWaterton/Wolink
164
-
165
- ### Gicisky
166
-
167
- Known by other names, e.g. 'Picksmart', and with white label brands
168
-
169
- - BLE ESLs
170
- - Official store is on [AliExpress](https://www.aliexpress.com/store/911771479/pages/all-items.html?productGroupId=40000001654819&spm=a2g0o.store_pc_home.pcShopHead_6000727597996.1_1)
171
- - Cheapest labels under £10 GBP / $13 USD
172
-
173
- ## Templating
174
-
175
- Templates are simply SVG files, to which expressions can be added to use SignalK data, with options to make it easier to read, like rounding or simplifying dates and times. The template can have sample data in the placeholder, so is easy to layout and visualize.
176
-
177
- ### Reframing
178
-
179
- There's some wiggle room with the `reframe` options to use a template that's a bit too small, or too large, for the label, although best results come from a template that's precisely matching the pixel height and width of the label. Next best is template that has the same aspect ratio, so it can be cleanly scaled. `crop` is the 2nd least worst, though if its only a handful of pixels its often not worth worring about a separate template and `crop` is just fine. `scale` is likely to look worst, since it will force an image in regardless of aspect ratio.
180
-
181
- ### Template Families (multiple panel sizes/colours)
182
-
183
- 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.
184
-
185
- 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.
186
-
187
- 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:
188
-
189
- 1. An exact width/height/colour-set match.
190
- 2. Failing that, width/height alone (any colour-set).
191
- 3. Failing that too, the nearest width, tie-broken by whichever file's own height/width ratio is closest to the device's.
192
-
193
- ### Template Source Specification
194
-
195
- In the `description` of the SVG text box, use a comma separated set of key value pairs to define the data source and formatting.
196
-
197
- #### SignalK Paths
198
-
199
- For example, `path=environment.forecast.description` uses the default data source (the `self` vessel context) and the named SignalK path. A bare path with no key/value pairs at all, e.g. just `environment.forecast.description`, is shorthand for the same thing. Overriding the default context can be done with `path=environment.forecast.description,context=vessels.urn:mrn:imo:mmsi:232345678` - the `context` value must match a real SignalK context exactly as it appears in the Data Browser.
200
-
201
- #### SignalK REST APIs
202
-
203
- The source can be overridden to use the SignalK server's Resources API instead. Change `source` to `resources` and specify which resource with `resource`. If there are multiple providers for the same resource, and they're not equally useful, then either set a default provider in SignalK, or use the `provider` tag to set the name.
204
-
205
- 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.
206
-
207
- #### Plugin Derived Data
208
-
209
- `source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Available paths:
210
-
211
- - `path=repainted` - the timestamp of the current repaint - for example `source=einklabel,path=repainted,format=local_datetime_short` to show when the label was last updated.
212
- - `path=local_zone` - a short zone name (e.g. `BST`) for the same timezone used for `local_time`/`day_mon`/`local_datetime_short` (see above) - a fallback for `environment.time.timezoneRegion,format=utc_offset` on installs that never publish that path, since it needs no SignalK metadata of its own. Falls back to a plain UTC offset like `GMT+1` where the host's locale has no real abbreviation for the zone.
213
- - `path=plugin_version` - expose the version of the eInk Label plugin itself.
214
-
215
- #### Customizing Output
216
-
217
- A `format` can be specified to make the value easier to understand. The supported formats are:
218
-
219
- - `local_time` - reduce a time stamp to just the time (H:M:S), omitting the date, and applying daylight savings if appropriate
220
- - `day_mon` - reduce a time stamp to day and month, e.g. `27 Jun`, applying daylight savings if appropriate
221
- - `local_datetime_short` - format a time stamp as day, abbreviated month, 2-digit year and 24h time, e.g. `21 Jun 26 18:05`, applying daylight savings if appropriate
222
- - `utc_offset` - Show a timezone in `UTC+01:00` style format
223
- - `position` - Format a `{ latitude, longitude }` value as decimal degrees with hemisphere letters, e.g. `56.6250°N 6.0700°W`
224
- - `raw` - Don't apply automatic SignalK unit conversion and symbol display (see below)
225
-
226
- SignalK's unit preferences are used to automatically convert a `signalk`-sourced numeric value to its preferred display unit, and append a unit symbol like `kt` or `m`, unless `format=raw` is specified to switch that off. However, when using plugin or API data there may be no path metadata to convert from (for example `signalk-tides` publishes tide data to the Resources API, and `level` is a raw metre value with no SignalK path of its own) - in these cases an explicit `category` can be given instead, and the unit preferences will be applied the same way, for example `category=depth` for the tides level figure.
227
-
228
- 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.
229
-
230
- ##### Common categories
231
-
232
- - `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
233
- - `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
234
- - `temperature` - Use the SignalK preferred temperature unit, make the conversion if needed, and tack on the unit name as a suffix
235
-
236
- Additionally, `round=n` can be used to round to limited decimal places.
237
-
238
- `default=<value>` substitutes `<value>` whenever the resolved value is missing (e.g. an unpublished path), instead of falling through to an empty string - useful anywhere a blank would be misread as a real answer. `default=` with nothing after the `=` still counts as set, deliberately defaulting to an empty string rather than leaving the fallback behaviour unchanged.
239
-
240
- These can all be combined as in `source=resources,resource=tides,provider=tides,path=extremes[2].level,category=depth,round=2`
241
-
242
- ### Non-Textual Fields (Images)
243
-
244
- The same `<desc>` mechanism works on an `<image>` element instead of a `<text>` element, for a value that's better shown as a picture than as text - a moon phase icon, a wind direction arrow, a weather condition glyph, and so on. Rather than substituting text, the resolved value picks one of a directory of `.svg` files to embed, by an extra required `assets=` key naming that directory - an `.assets/<name>` sub-directory looked up in your configured `templates` directory first, and the bundled `templates` directory otherwise. For example, the tide clock's moon phase icon uses:
245
-
246
- ```
247
- path=environment.moon.phaseName,assets=lunar_phases
248
- ```
249
-
250
- 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.
251
-
252
- 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.
253
-
254
- 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.
255
-
256
- ### Fonts
257
-
258
- Three font types are loaded by default, use the generic font family, or exact font name, in the SVG editor and choose size and weight (bold, semi-bold etc). Some labels will make a decent attempt to gray scale. Use the simple pure red, yellow, white, black to match the label's limited colour choice (some labels only offer black and white, or black/white/red). If a font can't be matched it will default to (sans-serif) Roboto.
259
-
260
- - `serif` - `Roboto Serif`
261
- - `sans-serif` - `Roboto`
262
- - `monospace` - `Roboto Mono`
263
-
264
- ## GenAI Rendering
265
-
266
- As an alternative to hand-designed SVG templates, a device's content can be generated by an LLM instead - this is provided entirely by a separate companion plugin, [`@rhizomatics/signalk-einklabel-genai-plugin`](https://github.com/rhizomatics/signalk-einklabel-genai-plugin), not by this core plugin. The base plugin (BLE painting, SVG templates) has nothing to do with any LLM SDK or API key, so installing it never pulls in GenAI dependencies unless you also explicitly install and enable the companion plugin - the split is deliberate, for anyone on a small/constrained install or who'd simply rather not have any GenAI code in their install at all.
267
-
268
- Once the companion plugin is installed and enabled, its own config screen handles provider/model/API-key selection (OpenAI, Anthropic, Google, xAI, DeepSeek, Moonshot AI, Ollama, or any other OpenAI-compatible endpoint), and its prompts show up as ordinary entries in this plugin's "Template" dropdown, suffixed (e.g. "forecast (GenAI)") - pick one exactly like you'd pick a `.svg` file. Under the hood it's a `TemplateProvider` (see [Extending](#extending) below) - the same extension mechanism a new vendor's hardware driver uses - so from this plugin's point of view it's just another way `templateName` gets resolved to an image.
269
-
270
- If the LLM call fails (network/API error, exhausted retries) or its response isn't renderable, this core plugin pushes its own bundled warning template instead of leaving the previous, possibly now-wrong, content on screen - so a stale weather prompt never quietly shows yesterday's forecast through today's storm. This is a generic safety net, not GenAI-specific: the exact same fallback covers a broken hand-authored template failing to render at all. The next scheduled repaint retries automatically. Since each repaint may call a (usually paid) LLM API, a GenAI-backed device should use Repaint Trigger `interval`, not `subscription`.
271
-
272
- See the companion plugin's own README for installing it, writing prompts, and its `esl-cli prompt`/`generate` commands for testing prompts without a device.
273
-
274
- ## Architecture
275
-
276
- The primary things managed and provided by the plugin are:
277
-
278
- - ESL Vendor
279
- - Sub-package per vendor
280
- - ESL Device
281
- - Metadata in the vendor package, using a `pid` or sometimes `pid` combined with `hwid` in the BLE results to pinpoint a model
282
- - SVG Template
283
- - SignalK API base URL
284
- - Used for automatic unit conversion on `signalk`-sourced numeric values and for resolving an explicit `category=` binding - neither has an in-process equivalent, both go via this server's own REST API
285
- - 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
286
- - 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
287
-
288
- ## Command Line Interface
289
-
290
- 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.
291
-
292
- - `vendors` - list supported vendors
293
- - `scan` - report supported devices found from a BLE scan
294
-
295
- See also the commands useful for debugging under [Developing Templates]
296
-
297
- - `render` - transform an SVG template and data into a PNG
298
- - `paint` - render an SVG template and data to a selected ESL
299
-
300
- The width, height, vertical offset and colour palette for the device are taken from the internal register of devices, however can be overridden on the command line with `-w/--width`, `--height`, `--voffset` and `--colours`. This could be used to help you choose what size of label to buy, or to get an unsupported label working.
301
-
302
- Left unset, both `render` and `paint` default `-w/--width`/`--height` to the template's own declared `width`/`height` (or `viewBox`) - neither command connects to a device just to size the render, since that would mean an extra BLE connect ahead of `paint`'s own, and doing two back-to-back is exactly the kind of churn that trips real BLE hardware.
303
-
304
- `paint` also takes `--reframe <mode>`, applied once it has connected and identified the device, for when the rendered image doesn't come out the same size as its actual panel (see [Reframing](#reframing) above):
305
-
306
- - `crop` (default) - keeps pixels 1:1, placed from the top-left; a bigger render is truncated to fit, a smaller one leaves the extra panel space blank
307
- - `scale` - stretches the rendered image onto the panel's exact dimensions (independently per axis, not preserving aspect ratio)
308
- - `fixed` - no adjustment; rejects a size mismatch with an error instead
309
-
310
- The main SignalK plugin offers the same choice per device (defaulting to `crop` there too) in each device's own config - "If the render doesn't match the panel size".
311
-
312
- `esl-cli` can also be extended with new subcommands by a `-r/--require`'d package - see [Extending](#extending) below - which is how [`@rhizomatics/signalk-einklabel-genai-plugin`](#genai-rendering) adds its own `prompt`/`generate` commands for testing prompts without a device.
313
-
314
- ( 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` )
315
-
316
- ### Scans from CLI
317
-
318
- The command line tools, run from inside the `.signalk` directory, can be used to help troubleshoot
319
-
320
- - Scan for longer, in this example 90 seconds
321
- - `npx esl-cli scan -d 90`
322
- - Scan for all BLE devices, whatever they are
323
- - `npx esl-cli scan -a`
324
-
325
- ## Extending
326
-
327
- ### Hardware
328
-
329
- Additional vendors and devices can be added by a separate npm package that implements the `VendorDriver` interface and registers itself - there's no scanning of installed packages, registration is always an explicit call by the extension's own code.
330
-
331
- - `import esl from '@rhizomatics/signalk-einklabel-plugin'; esl.registerVendorDriver(myDriver)`
332
- - In the SignalK runtime, call this from the extension's own plugin `start()`. In the CLI, load the extension with `esl-cli --require <module> <command>`.
333
- - Declare this package as a regular npm `dependency` in the extension package - **not** a `peerDependency`, per SignalK's own guidance that npm's peer-dependency resolution interacts poorly with the server's plugin install layout - and declare the SignalK-level relationship via `"signalk": { "requires": ["@rhizomatics/signalk-einklabel-plugin"] }` in the extension's own `package.json` instead, so the App Store can install/report it.
334
-
335
- ### Template Providers
336
-
337
- An alternative way to produce a device's content, alongside hand-authored SVG templates, can be added the same way - `esl.registerTemplateProvider({ suffix, listTemplates, render })`, from a separate package's own plugin `start()` (or `esl-cli --require <module>`). This is what [`@rhizomatics/signalk-einklabel-genai-plugin`](#genai-rendering) is: `listTemplates()` returns the entries it currently offers (already including its own `suffix`, e.g. `"forecast (GenAI)"`) for the "Template" dropdown, and `render(request)` - given the same signalk/resources/label context any SVG template's bindings get - returns a `Bitmap`, exactly as `SvgRenderer.render()` would. Rejecting from `render()` routes the repaint through this plugin's own generic fallback-warning template, the same as a broken hand-authored template failing to render.
338
-
339
- `esl.SvgRenderer`, `esl.buildLabel`, `esl.findBindingsInText`, and `esl.substituteBindingsInText` are also exported for a `TemplateProvider` extension to reuse - e.g. to resolve its own `{...}`-style placeholders against the request's context, or to rasterize whatever SVG it produces into the `Bitmap` its `render()` must return.
340
-
341
- ### Developing Templates
342
-
343
- See the [Templating] section for more details.
344
-
345
- 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).
346
-
347
- ![Example Field Definition](docs/assets/screenshots/inkscape_desc.png)
348
-
349
- The object ID and label aren't used by the plugin, only the description is used to define fields. You can also add in ordinary text fields without field definitions, as labels, logos, help text etc.
350
-
351
- Placeholder text isn't necessary, and is ignored by the plugin, but makes it much easier to visualize the result.
352
-
353
- 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.
354
-
355
- 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`.
356
-
357
- 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.
358
-
359
- ### Debugging Templates
360
-
361
- The `esl-cli` can be used to debug and validate templates quickly:
362
-
363
- - `render` - Render templates with SignalK data and write to a local PNG file
364
- - `paint` - Render templates with SignalK data and send to selected ESL device
365
- - `fields` - List the fields in the template, with the source specification and the rendered data value
366
- - `field` - Accept a source specification (outside of any template context) and return the rendered value if available
367
-
368
- Use `--help` to get the full set of arguments for any of the commands.
369
-
370
- ### Examples
371
-
372
- #### Paint Image Directly
373
-
374
- The label address previously discovered via `esl-cli scan`
375
-
376
- ```bash
377
- npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93
378
- ```
379
-
380
- If the label turns out to be a different size than the template (e.g. it's a 250x128 template on a 416x240 panel), that's normally rejected as a mismatch - add `--reframe` to fit it instead:
381
-
382
- ```bash
383
- npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93 --reframe scale
384
- ```
385
-
386
- #### Test Template Without Updating Label
387
-
388
- This will work even if you don't have a label, or even bluetooth. (The `-u` can be left out if your SignalK server running locally on default ports).
389
-
390
- ```bash
391
- npx esl-cli render -t templates/tides/250x128-BWRY.svg -o example.png -u http://localhost
392
- ```
393
-
394
- and this version will work even without a running SignalK server, using some pre-packaged example data:
395
-
396
- ```bash
397
- npx esl-cli render -t templates/tides/250x128-BWRY.svg -o example.png -e examples
398
- ```
399
-
400
- #### List all Fields and Rendered Values
401
-
402
- ```bash
403
- npx esl-cli fields -t templates/tide.svg -u http://localhost
404
- ```
405
-
406
- ```
407
- id spec value
408
- station.name source=resources,resource=tides,provider=tides,path=station.name Tobermory
409
- source.name. source=resources,resource=tides,provider=tides,path=station.source.name TICON-4
410
- last_repaint source=einklabel,path=repainted,format=local_datetime_short 30 Jun 26 00:08
411
- extremes.0 source=resources,resource=tides,provider=tides,path=extremes[0].label Low
412
- extremes.1 source=resources,resource=tides,provider=tides,path=extremes[1].label High
413
- extremes.2 source=resources,resource=tides,provider=tides,path=extremes[2].label Low
414
- timezoneRegion source=einklabel,path=local_zone BST
415
- lat source=resources,resource=tides,provider=tides,path=station.datums.LAT,category=depth 0.2m
416
- hat source=resources,resource=tides,provider=tides,path=station.datums.HAT,category=depth 5.2m
417
- extremes.2.level source=resources,resource=tides,provider=tides,path=extremes[2].level,category=depth 1.1m
418
- extremes.2.time source=resources,resource=tides,provider=tides,path=extremes[2].time,format=local_time 13:15
419
- extremes.1.level source=resources,resource=tides,provider=tides,path=extremes[1].level,category=depth 3.8m
420
- extremes.1.time source=resources,resource=tides,provider=tides,path=extremes[1].time,format=local_time 07:05
421
- extremes.0.time source=resources,resource=tides,provider=tides,path=extremes[0].time,format=local_time 01:21
422
- extremes.0.time-8 source=resources,resource=tides,provider=tides,path=extremes[0].time,format=day_mon 30 Jun
423
- extremes.0.time-8-5 source=resources,resource=tides,provider=tides,path=extremes[1].time,format=day_mon 30 Jun
424
- extremes.0.time-8-9 source=resources,resource=tides,provider=tides,path=extremes[2].time,format=day_mon 30 Jun
425
- extremes.0.level source=resources,resource=tides,provider=tides,path=extremes[0].level,category=depth 1.3m
426
- ```
427
-
428
- ### Offline Working
429
-
430
- `render` and `paint` need a `--url` argument to point to the SignalK server to retrieve data. If you don't have access to one, you can use `--example-data` or `-e` to point to a directory of example data, which is bundled with the plugin or available in GitHub at [examples](https://github.com/rhizomatics/signalk-einklabel-plugin/tree/main/examples). This also allows you to write templates for resource APIs that aren't available yet.
431
-
432
- - `vessels.json` - The standard SignalK vessel paths
433
- - `resources/xxxx.json` - The output of the `xxxx` resources API call
434
- - `categories.json` - SignalK unit categories needed for `category=depth` type formatting
435
-
436
- 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.
437
-
438
- ## Frequently Asked Questions
439
-
440
- ### I can't see my device as a choice on the drop-down list after scan
441
-
442
- SignalK plugins lack the ability to self-update after something like a scan, so first time round you may have to close the config and reload it to see this. Subsequently the plugin will remember all scanned devices, and only drop previously seen ones if it goes 24 hours without a positive scan or with failed paint attempts.
443
-
444
- Easiest way to solve this is to choose 'All Discovered Devices' in the device configuration, and it will paint any compatible devices it finds on future scans.
445
-
446
- ### Sometimes values are missing on the display
447
-
448
- 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.
449
-
450
- 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 it's still missing data.
451
-
452
- ### Times are showing incorrectly {#faq-timezone}
453
-
454
- If times are in the wrong timezone, or don't have daylight savings applied correctly,
455
- 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.
456
-
457
- 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.
458
-
459
- ### The ESL signal is too weak from my SignalK server
460
-
461
- Try a BLE proxy device, ESP32 is popular for this.
462
-
463
- ### Can't edit the text contents of SVG template in VSCode
464
-
465
- If you have an SVG viewer extension, this will show the image rather than allowing editing of text. To solve, right click on the file in VSCode _Explorer_ view and choose to edit with _Text Editor_.
466
-
467
- ### Description is set in InkScape but doesn't render
468
-
469
- Check if the text boxes are normal text or flowed text, and correct to normal text.
470
-
471
- ### My label just shows "CONTENT UNAVAILABLE"
472
-
473
- 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`](#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.
474
-
475
- ### SignalK starts before the Bluetooth daemon — does the plugin need `bluetoothd` running at boot?
476
-
477
- This and the next entry are about direct BlueZ mode only - if the "Use the SignalK BLE Manager API" setting is enabled, adapter/dongle lifecycle is the SignalK server's problem to manage once, for every BLE-consuming plugin, not this plugin's.
478
-
479
- The plugin retries BLE adapter initialisation with backoff (starting at 2s, capping at 30s) if `bluetoothd`/D-Bus isn't up yet when the plugin starts, so a slow-starting Bluetooth stack on boot will no longer strand it — it keeps retrying until the adapter appears rather than failing once and giving up. You'll see `BLE adapter not ready … — retrying in Ns …` in the SignalK logs in the meantime.
480
-
481
- That said, it's cleaner to fix the boot ordering at the systemd level so the plugin finds the adapter ready on its first attempt. If SignalK runs as a systemd service (`systemctl status signalk`) and its unit file has no `[Unit]` section (check with `systemctl cat signalk`), add one:
482
-
483
- ```bash
484
- sudo systemctl edit signalk.service
485
- ```
486
-
487
- This opens an override file — add:
488
-
489
- ```ini
490
- [Unit]
491
- After=bluetooth.target
492
- Wants=bluetooth.target
493
- ```
494
-
495
- Save and exit, then:
496
-
497
- ```bash
498
- sudo systemctl daemon-reload
499
- sudo systemctl restart signalk
500
- ```
501
-
502
- This tells systemd to start `bluetoothd` first and wait for it before starting SignalK, rather than relying on both racing to start in parallel at boot.
503
-
504
- ### Bluetooth Dongle with "No gpio to reset"
505
-
506
- Example log:
507
-
508
- ```
509
- Bluetooth: hci0: No gpio to reset Realtek device, ignoring
510
- Bluetooth: hci0: Unable to disable scanning: -110
511
- Bluetooth: hci0: command 0x2042 tx timeout
512
- Bluetooth: hci0: Opcode 0x2042 failed: -110
513
- ```
514
-
515
- This happens when USB autosuspend cycles the dongle in and out of low-power suspend while idle. When bluetoothd sends an HCI command while the device is suspended or mid-resume, it never gets answered — adapters like the popular ASUS USB-500 lack a GPIO to allow reset and its stuck, and spams logs.
516
-
517
- In these examples the dongle is for vendor `0b05` and product `190e`, adapt for your own devices, use `lsusb` to find out, and if there's no `lsusb` command, install the `usbutils` package.
518
-
519
- #### Example udev rule fix
520
-
521
- Following file created at `/etc/udev/rules.d/99-bt500-no-autosuspend.rules`
522
-
523
- ```
524
- # Disable USB autosuspend for the ASUS USB-BT500 (RTL8761BU, 0b05:190e).
525
- ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0b05", ATTR{idProduct}=="190e", TEST=="power/control", ATTR{power/control}="on"
526
- ```
527
-
528
- If `tlp` running to minimize power, it may have its own rules trying to suspend the Bluetooth dongle.
529
-
530
- #### Example tlp fix
531
-
532
- Following file created at /etc/tlp.d/99-bt500-no-autosuspend.conf
533
-
534
- ```
535
- USB_DENYLIST="0b05:190e"
536
- ```
33
+ - [Getting Started](https://signalk-einklabel.rhizomatics.org.uk/getting-started/) - pre-requisites, installation, setting up a label, and which labels to buy
34
+ - [Templates](https://signalk-einklabel.rhizomatics.org.uk/templates/) - how templates work, binding them to SignalK data, and designing your own
35
+ - [Examples](https://signalk-einklabel.rhizomatics.org.uk/examples/) - the bundled templates, with the sizes and data each one uses
36
+ - [Bluetooth](https://signalk-einklabel.rhizomatics.org.uk/bluetooth/) - choosing an adapter and keeping Bluetooth reliable
37
+ - [FAQ](https://signalk-einklabel.rhizomatics.org.uk/faq/) - answers to common problems
38
+ - [Command Line Interface](https://signalk-einklabel.rhizomatics.org.uk/cli/) - the `esl-cli` tool for testing templates and labels without SignalK
39
+ - [Extending](https://signalk-einklabel.rhizomatics.org.uk/extending/) - adding label hardware or new ways of producing content
537
40
 
538
41
  ## Other ESL and General eInk Resources
539
42
 
43
+ ### Components
44
+
540
45
  - [Open ePaper Link](https://openepaperlink.de) - Alternative open source firmware to flash onto eInk shelf labels, with Home Assistant integration.
541
46
  - [zhsunyco-esl](https://github.com/roxburghm/zhsunyco-esl) - Python interface
542
47
  - [WoLink](https://github.com/NickWaterton/Wolink) - Python interface and protocol analysis
@@ -545,9 +50,14 @@ USB_DENYLIST="0b05:190e"
545
50
  - [esp32-esl-system](https://github.com/giobauermeister/esp32-esl-system) - Docker and ESP32 based system for updating ESLs.
546
51
  - [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.
547
52
  - [ha-panda](https://github.com/moryoav/ha-panda) - Home Assistant integration for Panda ESLs ( a similar vendor to Zhsunyco).
53
+
54
+ ### Notes and Experiences
55
+
56
+ - [Cabalist Gicisky Image Notes](https://github.com/Cabalist/gicisky_image_notes)
548
57
  - [Dmitry.gr](https://dmitry.gr/?r=05.Projects&proj=29.%20eInk%20Price%20Tags) - Personal site of an ESL hacker
549
58
  - [Aaron Christobel](https://www.youtube.com/@atc1441) - YouTube channel of an ESL hacker.
550
59
  - [rbaron.net](https://rbaron.net/blog/2022/07/29/Daisy-chaining-multiple-electronic-shelf-labels) - Blog of an early ESL hacker.
60
+ ### Retail
551
61
  - [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.
552
62
  - [WaveShare](https://www.waveshare.com/product/displays/e-paper.htm) - Wide range of eInk displays for hardware projects, not limited to ESLs.
553
63
 
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { Colour } from "../devices/types";
3
+ import { Colour, CompressionFormat } from "../devices/types";
4
4
  import { ReframeMode } from "../render/reframe";
5
+ import { MirrorMode } from "../render/mirror";
5
6
  import { Binding } from "../render/binding";
6
7
  import { TemplateContext } from "../render/types";
7
8
  /** Tried in order when -u/--url is omitted (and -e/--example-data isn't given) - first one that answers wins. */
8
9
  export declare const DEFAULT_SIGNALK_URLS: string[];
9
10
  export declare function parseColours(code: string): Colour[];
10
11
  export declare function parseReframeMode(value: string): ReframeMode;
12
+ export declare function parseMirrorMode(value: string): MirrorMode;
13
+ export declare function parseCompressionFormat(value: string): CompressionFormat;
11
14
  /** Shared by every command that takes -u/--url and -e/--example-data - -e wins when both are present; when neither is given, probes DEFAULT_SIGNALK_URLS for a default. */
12
15
  export declare function assembleContext(opts: {
13
16
  url?: string;