@rhizomatics/signalk-einklabel-plugin 0.9.0 → 0.10.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.
package/CHANGELOG.md CHANGED
@@ -1,20 +1,42 @@
1
+ # 0.10.1
2
+
3
+ ## Watch Schedule Example
4
+ - New template to show current and next crew watch, using `signalk-watch-schedule` plugin
5
+
6
+ ## CLI / Debugging
7
+ - `fields` command now reports where text field definitions can't be interpreted
8
+ - Epoch style timestamps now automatically handled as if proper date time for format options
9
+
10
+ # 0.9.1
11
+
12
+ - Fix for upstream EventEmitter leak in `@naugehyde/node-ble`
13
+
1
14
  # 0.9.0
2
15
 
3
16
  ## Tide Clock Example
17
+
4
18
  - Rename `third_quarter` to `last_quarter` for moon phase icons in `templates/.assets/lunar_phases` to match Derived Data plugin
5
19
  - `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
20
+ - Added a simpler _250x128_ version of tide clock for 2.13" labels
21
+
7
22
  ## Fixes
23
+
8
24
  - Fix configuration JSON holding older versions of itself as subentries
9
- and data changes
25
+ and data changes
10
26
  - Fix most cases of scanned devices not appearing in device dropdown choice
27
+
11
28
  ## Improvements
12
- - Repaint state used to track which labels to repaint separately tracks the template
29
+
30
+ - Repaint state used to track which labels to repaint separately tracks the template
13
31
  - `nearestColour` algorithm in Zhsunyco driver supports ESLs that only have BWR or BW
32
+
14
33
  ## Device Selection
34
+
15
35
  - 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
36
  - Scan and explicit device selection only required for boats with multiple ESL devices
37
+
17
38
  ## Templating
39
+
18
40
  - Now support a directory of templates, where each is named like `416-240-BWRY.svg` to support same functions on different devices.
19
41
  - 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
42
  - `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,48 @@ 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
+ ### Watch Schedule
86
98
 
87
- Use the standard configuration option in the SignalK menu for the plugin.
99
+ ![Watch Schedule](docs/assets/screenshots/example_watch_schedule.png)
88
100
 
89
- ![Plugin Configuration](docs/assets/screenshots/plugin_config.png)
101
+ Template available as 416x240-BWRY for 3.7" ESLs.
102
+
103
+ #### Pre-requisites
104
+
105
+ - Source of `watch.current` and `watch.next` values
106
+ - `signalk-watch-schedule` plugin
107
+
108
+ ## Setting up a Label
109
+
110
+ Enable the plugin, and use the large **+** sign to add a label, which open up these fields.
111
+
112
+ ![Label Config](docs/assets/screenshots/label_config.png)
113
+
114
+ - _Friendly Name_ - Give the label any name (word or phrase) you like, for example 'Tide Clock'
115
+ - _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.
116
+ - _Template_ - Choose a built in template, or one you've added to the local templates directory
117
+ - _Repaint Trigger_- Do you want this to repaint every few hours (at a chosen minutes past hour), or when a SignalK path changes?
118
+ - If its a SignalK path, enter it next, for example `environment.tide.state`
119
+ - 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.
120
+
121
+ There are also two more advanced options, which can usually be ignored.
122
+
123
+ - _BLE AES key_ - Only needed if the default key doesn't work and you have a better alternative, otherwise ignore
124
+ - _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.)
125
+
126
+ 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
127
 
91
128
  ### Scanning for Devices
92
129
 
@@ -96,21 +133,18 @@ One other quirk is that some devices respond with a different name at different
96
133
 
97
134
  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
135
 
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:
136
+ ## Vendors
106
137
 
107
- #### Time Based
138
+ ### Zhsunyco
108
139
 
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.
140
+ Also known as 'Suny' and 'WOLink'.
110
141
 
111
- #### Path Subscription
142
+ - [BLE ESLs](https://www.zhsunyco.com/digital-display-solution-for-small-retail-business/ble-esl-solution/)
143
+ - The range of labels available on retail sites like AliExpress may be larger than on their corporate site
144
+ - In mid 2026, a 4 colour (BWRY) 3.7" label retailed for about $35, with quantity discounts for bulk sets
145
+ - Cheapest units are 2 colour 1.54", and they go up to 7.5"
112
146
 
113
- The devices will be painted when the plugin starts, and then every time the selected SignalK path changes.
147
+ Python code for a variety of their labels at https://github.com/roxburghm/zhsunyco-esl and https://github.com/NickWaterton/Wolink
114
148
 
115
149
  ## Templating
116
150
 
@@ -118,7 +152,9 @@ Templates are simply SVG files, to which expressions can be added to use SignalK
118
152
 
119
153
  ### Template Families (multiple panel sizes/colours)
120
154
 
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.
155
+ 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.
156
+
157
+ 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
158
 
123
159
  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
160
 
@@ -140,7 +176,7 @@ The source can be overridden to use the SignalK server's Resources API instead.
140
176
 
141
177
  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
178
 
143
- #### Plugin Data
179
+ #### Plugin Derived Data
144
180
 
145
181
  `source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Available paths:
146
182
 
@@ -163,7 +199,7 @@ SignalK's unit preferences are used to automatically convert a `signalk`-sourced
163
199
 
164
200
  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
201
 
166
- Common categories:
202
+ ##### Common categories
167
203
 
168
204
  - `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
169
205
  - `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
@@ -183,7 +219,7 @@ path=environment.moon.phaseName,assets=lunar_phases
183
219
 
184
220
  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
221
 
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.
222
+ 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
223
 
188
224
  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
225
 
@@ -195,19 +231,6 @@ Three font types are loaded by default, use the generic font family, or exact fo
195
231
  - `sans-serif` - `Roboto`
196
232
  - `monospace` - `Roboto Mono`
197
233
 
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
234
  ## Architecture
212
235
 
213
236
  The primary things managed and provided by the plugin are:
@@ -271,7 +294,9 @@ Placeholder text isn't necessary, and is ignored by the plugin, but makes it muc
271
294
 
272
295
  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
296
 
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.
297
+ 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`.
298
+
299
+ 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
300
 
276
301
  ### Debugging Templates
277
302
 
@@ -351,6 +376,10 @@ Try a BLE proxy device, ESP32 is popular for this.
351
376
 
352
377
  If you have an SVG viewer extension, this wll 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_.
353
378
 
379
+ ### Description is set in InkScape but doesn't render
380
+
381
+ Check if the text boxes are normal text or flowed text, and correct to normal text.
382
+
354
383
  ## Other ESL and General eInk Resources
355
384
 
356
385
  - [Open ePaper Link](https://openepaperlink.de) - Alternative open source firmware to flash onto eInk shelf labels, with Home Assistant integration.
package/dist/cli/index.js CHANGED
@@ -248,10 +248,12 @@ program
248
248
  const elements = doc.getElementsByTagName(tag);
249
249
  for (let i = 0; i < elements.length; i++) {
250
250
  const element = elements.item(i);
251
- const desc = element?.getElementsByTagName("desc").item(0);
252
- if (!element || !desc?.textContent)
251
+ if (!element)
253
252
  continue;
254
253
  const id = element.getAttribute("id") ?? `${tag}#${i}`;
254
+ const desc = element.getElementsByTagName("desc").item(0);
255
+ if (!desc?.textContent)
256
+ continue;
255
257
  try {
256
258
  rows.push({ id, tag, desc: desc.textContent, binding: (0, binding_1.parseBinding)(desc.textContent) });
257
259
  }
@@ -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`);
@@ -57,6 +57,8 @@ function parseTimestamp(value) {
57
57
  return luxon_1.DateTime.fromJSDate(value, { zone: "utc" });
58
58
  if (typeof value === "string")
59
59
  return luxon_1.DateTime.fromISO(value, { zone: "utc" });
60
+ if (typeof value === "number")
61
+ return luxon_1.DateTime.fromMillis(value, { zone: "utc" });
60
62
  return undefined;
61
63
  }
62
64
  /**
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.10.0",
4
4
  "description": "Display SignalK data on eInk Electronic Shelf Labels",
5
5
  "keywords": [
6
6
  "ble",
@@ -8,6 +8,7 @@
8
8
  "eink",
9
9
  "esl",
10
10
  "instrument",
11
+ "watch-schedule",
11
12
  "signalk",
12
13
  "signalk-category-hardware",
13
14
  "signalk-category-instruments",
@@ -79,7 +80,9 @@
79
80
  "appIcon": "docs/assets/icons/icon_tmp.png",
80
81
  "screenshots": [
81
82
  "docs/assets/screenshots/example_tidal_clock.png",
82
- "docs/assets/screenshots/plugin_config.png"
83
+ "docs/assets/screenshots/example_watch_schedule.png",
84
+ "docs/assets/screenshots/plugin_config.png",
85
+ "docs/assets/screenshots/label_config.png"
83
86
  ]
84
87
  }
85
88
  }
@@ -0,0 +1,69 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="250"
4
+ height="128"
5
+ viewBox="0 0 250 128"
6
+ version="1.1"
7
+ id="svg1"
8
+ sodipodi:docname="250x128-BWRY.svg"
9
+ inkscape:version="1.3.2 (091e20e, 2023-11-25)"
10
+ xml:space="preserve"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
15
+ id="defs1" /><sodipodi:namedview
16
+ id="namedview1"
17
+ pagecolor="#ffffff"
18
+ bordercolor="#000000"
19
+ borderopacity="0.25"
20
+ inkscape:showpageshadow="2"
21
+ inkscape:pageopacity="0.0"
22
+ inkscape:pagecheckerboard="0"
23
+ inkscape:deskcolor="#d1d1d1"
24
+ inkscape:zoom="1.3906433"
25
+ inkscape:cx="69.751891"
26
+ inkscape:cy="106.78511"
27
+ inkscape:window-width="1488"
28
+ inkscape:window-height="674"
29
+ inkscape:window-x="24"
30
+ inkscape:window-y="33"
31
+ inkscape:window-maximized="0"
32
+ inkscape:current-layer="svg1"
33
+ showgrid="true"
34
+ showguides="true"><inkscape:grid
35
+ id="grid1"
36
+ units="px"
37
+ originx="0"
38
+ originy="0"
39
+ spacingx="1"
40
+ spacingy="1"
41
+ empcolor="#0099e5"
42
+ empopacity="0.30196078"
43
+ color="#0099e5"
44
+ opacity="0.14901961"
45
+ empspacing="5"
46
+ dotted="false"
47
+ gridanglex="30"
48
+ gridanglez="30"
49
+ visible="true" /></sodipodi:namedview><rect
50
+ x="1.0169492"
51
+ y="2.0798673e-08"
52
+ width="249.17073"
53
+ height="129.97897"
54
+ fill="#ffffff"
55
+ id="rect1"
56
+ style="stroke-width:0.569552" /><text
57
+ id="last_repaint"
58
+ x="221.55899"
59
+ y="104.94675"
60
+ font-size="18"
61
+ font-family="serif"
62
+ fill="black"
63
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.6667px;font-family:serif;-inkscape-font-specification:'serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffff00;stroke-width:1.19309"
64
+ transform="scale(0.85412559,1.170788)"><desc
65
+ id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc><tspan
66
+ sodipodi:role="line"
67
+ id="tspan3"
68
+ x="221.55899"
69
+ y="104.94675">27 Jun 26 18:05</tspan></text></svg>
@@ -0,0 +1,86 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="416"
4
+ height="240"
5
+ viewBox="0 0 416 240"
6
+ version="1.1"
7
+ id="svg1"
8
+ sodipodi:docname="416x240-BWRY.svg"
9
+ inkscape:version="1.3.2 (091e20e, 2023-11-25)"
10
+ xml:space="preserve"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
15
+ id="defs1" /><sodipodi:namedview
16
+ id="namedview1"
17
+ pagecolor="#ffffff"
18
+ bordercolor="#000000"
19
+ borderopacity="0.25"
20
+ inkscape:showpageshadow="2"
21
+ inkscape:pageopacity="0.0"
22
+ inkscape:pagecheckerboard="0"
23
+ inkscape:deskcolor="#d1d1d1"
24
+ inkscape:zoom="1.3906433"
25
+ inkscape:cx="155.3238"
26
+ inkscape:cy="187.32338"
27
+ inkscape:window-width="1488"
28
+ inkscape:window-height="674"
29
+ inkscape:window-x="24"
30
+ inkscape:window-y="33"
31
+ inkscape:window-maximized="0"
32
+ inkscape:current-layer="svg1"
33
+ showgrid="true"
34
+ showguides="true"><inkscape:grid
35
+ id="grid1"
36
+ units="px"
37
+ originx="0"
38
+ originy="0"
39
+ spacingx="1"
40
+ spacingy="1"
41
+ empcolor="#0099e5"
42
+ empopacity="0.30196078"
43
+ color="#0099e5"
44
+ opacity="0.14901961"
45
+ empspacing="5"
46
+ dotted="false"
47
+ gridanglex="30"
48
+ gridanglez="30"
49
+ visible="true" /></sodipodi:namedview><rect
50
+ x="1.0169492"
51
+ y="2.0798673e-08"
52
+ width="416"
53
+ height="240"
54
+ fill="#ffffff"
55
+ id="rect1" /><rect
56
+ style="fill:#ffff00;stroke:#000000;stroke-width:0.614045"
57
+ id="rect2"
58
+ width="416.94916"
59
+ height="18.410578"
60
+ x="-24.928873"
61
+ y="-242.40596"
62
+ transform="matrix(1,0,-0.1068988,-0.99426991,0,0)" /><text
63
+ id="last_repaint"
64
+ x="4.3477793"
65
+ y="201.9895"
66
+ font-size="18"
67
+ font-family="serif"
68
+ fill="black"
69
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:sans-serif;-inkscape-font-specification:'Roboto',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.19309"
70
+ transform="scale(0.85412558,1.170788)"><desc
71
+ id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc><tspan
72
+ sodipodi:role="line"
73
+ id="tspan3">27 Jun 26 18:05</tspan></text><text
74
+ id="plugin_version"
75
+ x="194.07487"
76
+ y="204.51942"
77
+ font-size="10.373px"
78
+ font-family="sans-serif"
79
+ fill="#ffff00"
80
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;direction:rtl;fill:#000000;stroke-width:0.864419"
81
+ transform="scale(0.86441854,1.156847)"><desc
82
+ id="desc16">source=einklabel,path=plugin_version</desc><tspan
83
+ sodipodi:role="line"
84
+ id="tspan1"
85
+ x="194.07487"
86
+ y="204.51942">v0.0.1</tspan></text></svg>
@@ -0,0 +1,199 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="416"
4
+ height="240"
5
+ viewBox="0 0 416 240"
6
+ version="1.1"
7
+ id="svg1"
8
+ sodipodi:docname="416x240-BWRY.svg"
9
+ inkscape:version="1.3.2 (091e20e, 2023-11-25)"
10
+ xml:space="preserve"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
15
+ id="defs1"><rect
16
+ x="176.17746"
17
+ y="50.336416"
18
+ width="168.98654"
19
+ height="53.931875"
20
+ id="rect8" /><rect
21
+ x="53.931875"
22
+ y="29.482758"
23
+ width="134.47014"
24
+ height="40.988225"
25
+ id="rect4" /><rect
26
+ x="176.17746"
27
+ y="50.336414"
28
+ width="168.98654"
29
+ height="53.931873"
30
+ id="rect8-3" /><rect
31
+ x="176.17746"
32
+ y="50.336414"
33
+ width="168.98654"
34
+ height="53.931873"
35
+ id="rect8-3-5" /><rect
36
+ x="176.17746"
37
+ y="50.336414"
38
+ width="168.98654"
39
+ height="53.931873"
40
+ id="rect8-3-0" /><rect
41
+ x="176.17746"
42
+ y="50.336414"
43
+ width="168.98654"
44
+ height="53.931873"
45
+ id="rect8-3-5-4" /><rect
46
+ x="176.17746"
47
+ y="50.336414"
48
+ width="168.98654"
49
+ height="53.931873"
50
+ id="rect8-0" /><rect
51
+ x="53.931873"
52
+ y="29.482758"
53
+ width="134.47014"
54
+ height="40.988224"
55
+ id="rect4-3" /><rect
56
+ x="53.931873"
57
+ y="29.482758"
58
+ width="134.47014"
59
+ height="40.988224"
60
+ id="rect4-4" /><rect
61
+ x="176.17746"
62
+ y="50.336414"
63
+ width="168.98654"
64
+ height="53.931873"
65
+ id="rect8-0-4" /></defs><sodipodi:namedview
66
+ id="namedview1"
67
+ pagecolor="#ffffff"
68
+ bordercolor="#000000"
69
+ borderopacity="0.25"
70
+ inkscape:showpageshadow="2"
71
+ inkscape:pageopacity="0.0"
72
+ inkscape:pagecheckerboard="0"
73
+ inkscape:deskcolor="#d1d1d1"
74
+ inkscape:zoom="0.98333331"
75
+ inkscape:cx="210"
76
+ inkscape:cy="78.305087"
77
+ inkscape:window-width="1488"
78
+ inkscape:window-height="675"
79
+ inkscape:window-x="24"
80
+ inkscape:window-y="33"
81
+ inkscape:window-maximized="0"
82
+ inkscape:current-layer="svg1"
83
+ showgrid="true"
84
+ showguides="true"><inkscape:grid
85
+ id="grid1"
86
+ units="px"
87
+ originx="0"
88
+ originy="0"
89
+ spacingx="1"
90
+ spacingy="1"
91
+ empcolor="#0099e5"
92
+ empopacity="0.30196078"
93
+ color="#0099e5"
94
+ opacity="0.14901961"
95
+ empspacing="5"
96
+ dotted="false"
97
+ gridanglex="30"
98
+ gridanglez="30"
99
+ visible="true" /></sodipodi:namedview><rect
100
+ x="1.0169492"
101
+ y="2.0798673e-08"
102
+ width="416"
103
+ height="240"
104
+ fill="#ffffff"
105
+ id="rect1"
106
+ inkscape:label="label" /><rect
107
+ style="fill:#ffff00;stroke:#000000;stroke-width:0.614045"
108
+ id="rect2"
109
+ width="416.94916"
110
+ height="18.410578"
111
+ x="-24.928873"
112
+ y="-242.40596"
113
+ inkscape:label="bg_footer" /><text
114
+ id="last_repaint"
115
+ x="3.71355"
116
+ y="236.486883"
117
+ font-size="18"
118
+ font-family="serif"
119
+ fill="black"
120
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:sans-serif;-inkscape-font-specification:'Roboto',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.19309"><desc
121
+ id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc>27 Jun 26 18:05</text><text
122
+ id="plugin_version"
123
+ x="167.761916"
124
+ y="236.597677"
125
+ font-size="10.373px"
126
+ font-family="sans-serif"
127
+ fill="#ffff00"
128
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;fill:#000000;stroke-width:0.864419"><desc
129
+ id="desc16">source=einklabel,path=plugin_version</desc>v0.0.1</text><text
130
+ id="watch_system"
131
+ x="321.466726"
132
+ y="236.116556"
133
+ font-size="10.373px"
134
+ font-family="sans-serif"
135
+ fill="#ffff00"
136
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:serif;-inkscape-font-specification:'serif, @wght=100';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variation-settings:'wght' 100;fill:#000000;stroke-width:0.864419"><desc
137
+ id="desc7">watch.system.name</desc>2-on / 2-off</text><text
138
+ id="text4-6"
139
+ x="8.407525"
140
+ y="31.738578"
141
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:32px;font-family:serif;-inkscape-font-specification:'serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;fill:#808080;stroke:none;stroke-width:0.612283;stroke-opacity:1"
142
+ inkscape:label="title">Watches</text><text
143
+ id="current_start"
144
+ x="7.3719606"
145
+ y="120.02129"
146
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.3333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:#ffff00;stroke-width:0.56247"><desc
147
+ id="desc1">path=watch.current.startTime,format=local_time</desc>12:00</text><text
148
+ id="current_end"
149
+ x="117.756"
150
+ y="120.04606"
151
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.3333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:#ffff00;stroke-width:0.612;stroke-dasharray:none"><desc
152
+ id="desc2">path=watch.current.endTime,format=local_time</desc>16:00</text><text
153
+ id="next_team"
154
+ x="219.44624"
155
+ y="205.76323"
156
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.3333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:none;stroke-width:0.612283"><desc
157
+ id="desc5">watch.next.teamName</desc>Team 2</text><text
158
+ id="current_team"
159
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:32px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;fill-opacity:1;stroke:#ffff00;stroke-width:0.73;stroke-dasharray:none;stroke-opacity:1"
160
+ x="221.56224"
161
+ y="121.52837"><desc
162
+ id="desc5-6">watch.current.teamName</desc>Team 1</text><text
163
+ id="next_start"
164
+ x="6.5166879"
165
+ y="205.07277"
166
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:none;stroke-width:0.580975"
167
+ inkscape:label="next_start"><desc
168
+ id="desc4">path=watch.next.startTime,format=local_time</desc>16:00</text><text
169
+ id="next_end"
170
+ x="119.80927"
171
+ y="205.40628"
172
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;fill:#1a1a1a;stroke:none;stroke-width:0.612283"><desc
173
+ id="desc3">path=watch.next.endTime,format=local_time</desc>20:00</text>
174
+
175
+ <rect
176
+ style="fill:#ff0000;stroke:none;stroke-width:2.621;stroke-dasharray:none;stroke-opacity:1"
177
+ id="rect15"
178
+ width="397.62714"
179
+ height="35.59322"
180
+ x="9.1525431"
181
+ y="52.881355"
182
+ inkscape:label="bg_current" /><text
183
+ id="text4"
184
+ x="20.708879"
185
+ y="80.673174"
186
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;fill:#f9f9f9;stroke:#fefe54;stroke-width:0.612283"
187
+ inkscape:label="label_current">Current</text><rect
188
+ style="fill:#666666;stroke:none;stroke-width:2.6277;stroke-dasharray:none;stroke-opacity:1"
189
+ id="rect15-7"
190
+ width="399.66104"
191
+ height="35.59322"
192
+ x="8.5084782"
193
+ y="140.50848"
194
+ inkscape:label="bg_next" /><text
195
+ id="text4-7"
196
+ x="22.098717"
197
+ y="167.28336"
198
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;fill:#f9f9f9;stroke:#fefe54;stroke-width:0.612283"
199
+ inkscape:label="label_next">Next</text></svg>