@rhizomatics/signalk-einklabel-plugin 0.6.0 → 0.6.2

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,47 +1,92 @@
1
+ # 0.6.2
2
+
3
+ - Auto default SignalK URL if `-u` or `-e` not passed as CLI arguments
4
+ - Default timeout for painting a device screen is now 60 seconds
5
+ - Code now prettified, and prettier check added to CI
6
+ - Test coverage improved and now reported
7
+
8
+ # 0.6.1
9
+
10
+ - Corrected tide lunar phase path from `environment.moonPhase.name` to `environment.moon.phaseName`
11
+
1
12
  # 0.6.0
2
- - Added ability to select images for inclusion in SVG templated based on SignalK value
13
+
14
+ - Added ability to select images for inclusion in SVG templates based on SignalK path value
15
+ - Pass a directory of assets, and it will pick the SVG file matching the SignalK value
16
+ - Matching will cope with "Waning Gibbous" -> `waning_gibbous.svg`
17
+ - If no match, image will be blank
3
18
  - New image selection used to add lunar phase to tide clock example
4
- - Requires a source for `environment.moonPhase.name`, for example the `dervived-data` plugin
19
+ - Requires a source for `environment.moon.phaseName`, for example the `dervived-data` plugin
20
+ - New `resources` directory for composable SVG assets
21
+
5
22
  # 0.5.1
23
+
6
24
  - Work around `resvg-wasm` font limitations by overriding generic font family with matching font name prior to rendering
25
+
7
26
  # 0.5.0
27
+
8
28
  ## Font Handling
29
+
9
30
  - Update set of built-in fonts for consistent Roboto monospace and serif
10
31
  - Update `tide.svg` template to use explicit font names to work around `resvg-wasm` limitations
32
+
11
33
  ## Offline Design
34
+
12
35
  - Offline template development now possible using example data,
13
36
  - Pass `-e` or `--example-data` as a CLI argument, pointing to a set of example JSON files
14
37
  - Examples of example data provided in plugin and at https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/examples
15
38
  - Works with `render`,`paint`,`field` and `fields`
39
+
16
40
  # 0.4.7
41
+
17
42
  - Fix node-ble imports
43
+
18
44
  # 0.4.6
45
+
19
46
  - Correct `node-ble` dependency to `@naugehyde/node-ble`
47
+
20
48
  # 0.4.5
49
+
21
50
  - Updated `tide.svg` layout
22
51
  - Added SignalK standard Github Actions workflow
52
+
23
53
  # 0.4.4
54
+
24
55
  - Update `node-ble` to new packaging
56
+
25
57
  # 0.4.3
58
+
26
59
  - Fixed datetime values when running from plugin were blank while CLI was fine
27
60
  - Added an example Resources API output from signalk-tides plugin
28
61
  - Relaid out the example tide clock, adding the tidal range (LAT to HAT) and the source of tide data
29
62
  - Alternative source of time zone info, `source=einklabel,path=local_zone`
63
+
30
64
  # 0.4.2
65
+
31
66
  - Fix High/Low display for tide clock template
32
67
  - Fix last repaint time for display being hour out
33
68
  - Change default name of template directory to 'einklabel/templates'
69
+
34
70
  # 0.4.1
71
+
35
72
  - Packaging fixes
73
+
36
74
  # 0.4.0
75
+
37
76
  - Added `einklabel` as a `source` for template fields, and `repainted` as path
38
77
  - Added `local_datetime_short` as a datetime format option, for `27 Jun 26 18:05` style output
39
78
  - Add Last Repainted field to the Tide Clock example
79
+
40
80
  # 0.3.2
81
+
41
82
  - Renamed to signalk-einklabel-plugin
42
83
  - Added core test suite
84
+
43
85
  # 0.3.1
86
+
44
87
  - Correct name, and include changelog
88
+
45
89
  # 0.3.0
90
+
46
91
  - First published beta release
47
- - Tested publishing Tide Clock on interval to a Zhunyco 3.7" display
92
+ - Tested publishing Tide Clock on interval to a Zhunyco 3.7" display
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,41 +1,58 @@
1
1
  # eInk Labels for SignalK
2
2
 
3
- ** BETA - basic functionality, limited vendor/product support **
3
+ [![npm version](https://img.shields.io/npm/v/@rhizomatics/signalk-einklabel-plugin.svg)](https://www.npmjs.com/package/@rhizomatics/signalk-einklabel-plugin)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@rhizomatics/signalk-einklabel-plugin.svg)](https://www.npmjs.com/package/@rhizomatics/signalk-einklabel-plugin)
5
+ [![SignalK Plugin CI](https://github.com/rhizomatics/signalk-einklabel-plugin/actions/workflows/signalk-ci.yml/badge.svg)](https://github.com/rhizomatics/signalk-einklabel-plugin/actions/workflows/signalk-ci.yml)
6
+ [![codecov](https://img.shields.io/codecov/c/github/rhizomatics/signalk-einklabel-plugin)](https://codecov.io/gh/rhizomatics/signalk-einklabel-plugin)
7
+ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
8
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/LICENSE)
4
9
 
5
- A SignalK plugin to display data from SignalK paths, APIs and plugins on Electronic Shelf Labels over a Bluetooth Low Energy (BLE) connection using simple SVG templates.
10
+ ** Fully working but limited vendor/product support **
6
11
 
7
- Electronic Shelf Labels (ESLs) are [eInk](https://en.wikipedia.org/wiki/E_Ink) devices that consume very little battery energy, presuming they are not constantly updated - the battery is used only when the display changes, and a periodic BLE check for incoming changes. Perfect for info that changes only once or twice a day, like tidal information.
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.
13
+
14
+ ## What is an ESL?
15
+
16
+ Electronic Shelf Labels are [eInk](https://en.wikipedia.org/wiki/E_Ink) devices that consume very little battery energy, presuming they are not constantly updated - the battery is used only when the display changes, and a periodic BLE check for incoming changes. Perfect for info that changes only once or twice a day, like tidal information.
8
17
 
9
18
  Since they are designed to be used in large quantity in small shops, they are cheap and simple devices. Earlier models required dedicated controllers, or updates over Wifi or NFC, whereas many modern ones are standalone BLE devices that can be updated from a phone or server.
10
19
 
11
- 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.
20
+ Being battery operated, they can be stuck on anywhere without wiring - the only constraints are bluetooth range, visibility (they need ambient light since the display is more like paper than a traditional lit-up electronic display) and out of the weather since the devices are intended for indoor use.
12
21
 
13
22
  ## Pre-requisites
14
23
 
15
- Most of this is 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.
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
+
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.
16
27
 
17
28
  1. A SignalK server, preferably running Linux (MacOS does weird things with bluetooth)
18
29
  2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy), which is Bluetooth v4.0 or higher
19
- - Bluetooth adapters for Linux can be tricky, TP-Link UB400 and Asus USB-BT500 are two well-known and available ones
20
- - Some Raspberry Pi models come with suitable Bluetooth it built-in
21
- - Don't worry about the very latest Bluetooth versions, 4.0 is basic, 5.0 is nice
22
- - Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
30
+
31
+ - Bluetooth adapters for Linux can be tricky, TP-Link UB400 and Asus USB-BT500 are two well-known and available ones
32
+ - Some Raspberry Pi models come with suitable Bluetooth it built-in
33
+ - Don't worry about the very latest Bluetooth versions, 4.0 is basic, 5.0 is nice
34
+ - Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
35
+
23
36
  3. `bluez` package installed in Linux
24
- - No need to do this if you have a Raspberry Pi with recent Raspian version, since bluez comes built in.
25
- - If you're not running a Raspberry Pi, then ensure that the `dbus` package is installed
37
+
38
+ - No need to do this if you have a Raspberry Pi with recent Raspian version, since bluez comes built in.
39
+ - If you're not running a Raspberry Pi, then ensure that the `dbus` package is installed
40
+
26
41
  4. One or more supported Electronic Shelf Labels
27
- - The label used for testing this is the [ZhunyCo 3.7 BRWY](https://www.aliexpress.com/item/1005010050104435.html)
42
+
43
+ - The label used for testing this is the [ZhunyCo 3.7 BRWY](https://www.aliexpress.com/item/1005010050104435.html)
44
+
28
45
  5. Correct time zone set on server if local time is to be shown on display
29
- - Use `raspi-config` on a Raspberry Pi, or `timedatectl` on a Linux server
30
- - If not set, everything will work, but you may see the wrong zone or not have daylight savings applied
31
46
 
32
- 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.
47
+ - Use `raspi-config` on a Raspberry Pi, or `timedatectl` on a Linux server
48
+ - If not set, everything will work, but you may see the wrong zone or not have daylight savings applied
33
49
 
50
+ 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.
34
51
 
35
52
  ## Installation
36
53
 
37
54
  Look for **eInk Label Instrument** in the [SignalK AppStore]() on your
38
- server ( under *Apps & Plugins* on the latest version).
55
+ server ( under _Apps & Plugins_ on the latest version).
39
56
 
40
57
  ### Using Outside of SignalK
41
58
 
@@ -53,7 +70,7 @@ npm install @rhizomatics/signalk-einklabel-plugin
53
70
 
54
71
  The tide clock needs the [signalk-tides](https://github.com/openwatersio/signalk-tides) plugin to be installed and publishing tides to the Resources API. The [tide.svg](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/templates/tide.svg) can be customized to run with other APIs or take data only from SignalK data paths.
55
72
 
56
- To show the lunar phase, the `environment.moonPhase.name` path is required, which can
73
+ To show the lunar phase, the `environment.moon.phaseName` path is required, which can
57
74
  be easily achieved by installing and configuring the `derived-data` plugin.
58
75
 
59
76
  ## Templating
@@ -76,19 +93,19 @@ The source can be overridden to use the SignalK server's Resources API instead.
76
93
 
77
94
  `source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Available paths:
78
95
 
79
- * `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.
80
- * `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.
96
+ - `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.
97
+ - `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.
81
98
 
82
99
  #### Customizing Output
83
100
 
84
101
  A `format` can be specified to make the value easier to understand. The supported formats are:
85
102
 
86
- * `local_time` - reduce a time stamp to just the time (H:M:S), omitting the date, and applying daylight savings if appropriate
87
- * `day_mon` - reduce a time stamp to day and month, e.g. `27 Jun`, applying daylight savings if appropriate
88
- * `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
89
- * `utc_offset` - Show a timezone in `UTC+01:00` style format
90
- * `position` - Format a `{ latitude, longitude }` value as decimal degrees with hemisphere letters, e.g. `56.6250°N 6.0700°W`
91
- * `raw` - Don't apply automatic SignalK unit conversion and symbol display (see below)
103
+ - `local_time` - reduce a time stamp to just the time (H:M:S), omitting the date, and applying daylight savings if appropriate
104
+ - `day_mon` - reduce a time stamp to day and month, e.g. `27 Jun`, applying daylight savings if appropriate
105
+ - `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
106
+ - `utc_offset` - Show a timezone in `UTC+01:00` style format
107
+ - `position` - Format a `{ latitude, longitude }` value as decimal degrees with hemisphere letters, e.g. `56.6250°N 6.0700°W`
108
+ - `raw` - Don't apply automatic SignalK unit conversion and symbol display (see below)
92
109
 
93
110
  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.
94
111
 
@@ -96,9 +113,9 @@ Note that for dates and times, the server timezone must be set correctly, for ex
96
113
 
97
114
  Common categories:
98
115
 
99
- * `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
100
- * `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
101
- * `temperature` - Use the SignalK preferred temperature unit, make the conversion if needed, and tack on the unit name as a suffix
116
+ - `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
117
+ - `speed` - Use the SignalK preferred speed unit, make the conversion if needed, and tack on the unit name as a suffix
118
+ - `temperature` - Use the SignalK preferred temperature unit, make the conversion if needed, and tack on the unit name as a suffix
102
119
 
103
120
  Additionally, `round=n` can be used to round to limited decimal places.
104
121
 
@@ -109,7 +126,7 @@ These can all be combined as in `source=resources,resource=tides,path=extremes[2
109
126
  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 (resolved relative to the template file itself, so a bundled template and a user override both work the same way). For example, the tide clock's moon phase icon uses:
110
127
 
111
128
  ```
112
- path=environment.moonPhase.name,assets=../resources/svg/lunar_phases
129
+ path=environment.moon.phaseName,assets=../resources/svg/lunar_phases
113
130
  ```
114
131
 
115
132
  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 simply omitted from that render - no broken image, no placeholder, nothing shown.
@@ -120,9 +137,9 @@ This is a general mechanism, not specific to moon phases - any `source`/`context
120
137
 
121
138
  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.
122
139
 
123
- * `serif` - `Roboto Serif`
124
- * `sans-serif` - `Roboto`
125
- * `monospace` - `Roboto Mono`
140
+ - `serif` - `Roboto Serif`
141
+ - `sans-serif` - `Roboto`
142
+ - `monospace` - `Roboto Mono`
126
143
 
127
144
  ## Command Line Interface
128
145
 
@@ -132,12 +149,13 @@ To get fast feedback on templates and shelf devices without updating and configu
132
149
  - `scan` - report supported devices found from a BLE scan
133
150
 
134
151
  See also the commands useful for debugging under [Developing Templates]
152
+
135
153
  - `render` - transform an SVG template and data into a PNG
136
154
  - `paint` - render an SVG template and data to a selected ESL
137
155
 
138
156
  The width, height, vertical offset and colour palette for the device is taken from the internal register of devices, however can be overridden on the command line. This could be used to help you choose what size of label to buy, or to get an unsupported label working.
139
157
 
140
- ( 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 `npm run cli -- command --args` )
158
+ ( 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` )
141
159
 
142
160
  ## Vendors
143
161
 
@@ -156,12 +174,12 @@ Python code for a variety of their labels at https://github.com/roxburghm/zhsuny
156
174
 
157
175
  The primary things managed and provided by the plugin are:
158
176
 
159
- * ESL Vendor
177
+ - ESL Vendor
160
178
  - Sub-package per vendor
161
- * ESL Device
179
+ - ESL Device
162
180
  - Metadata in the vendor package, using a `pid` or sometimes `pid` combined with `hwid` in the BLE results to pinpoint a model
163
- * SVG Template
164
- * SignalK API base URL
181
+ - SVG Template
182
+ - SignalK API base URL
165
183
  - 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
166
184
  - 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
167
185
  - 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
@@ -194,17 +212,17 @@ Due to a limitation in the `resvg-wasm` library used to turn SVGs into images, t
194
212
 
195
213
  The `esl-cli` can be used to debug and validate templates quickly:
196
214
 
197
- * `render` - Render templates with SignalK data and write to a local PNG file
198
- * `paint` - Render templates with SignalK data and send to selected ESL device
199
- * `fields` - List the fields in the template, with the source specification and the rendered data value
200
- * `field` - Accept a source specification (outside of any template context) and return the rendered value if available
215
+ - `render` - Render templates with SignalK data and write to a local PNG file
216
+ - `paint` - Render templates with SignalK data and send to selected ESL device
217
+ - `fields` - List the fields in the template, with the source specification and the rendered data value
218
+ - `field` - Accept a source specification (outside of any template context) and return the rendered value if available
201
219
 
202
220
  ### Examples
203
221
 
204
222
  #### List all Fields and Rendered Values
205
223
 
206
224
  ```bash
207
- npm run cli -- fields -t templates/tide.svg -u http://localhost
225
+ npx esl-cli fields -t templates/tide.svg -u http://localhost
208
226
  ```
209
227
 
210
228
  ```
@@ -235,4 +253,6 @@ extremes.0.level source=resources,resource=tides,path=extremes[0].level,
235
253
 
236
254
  - `vessels.json` - The standard SignalK vessel paths
237
255
  - `resources/xxxx.json` - The output of the `xxxx` resources API call
238
- - `categories.json` - SignalK unit categories needed for `category=depth` type formatting
256
+ - `categories.json` - SignalK unit categories needed for `category=depth` type formatting
257
+
258
+ 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.
package/dist/cli/index.js CHANGED
@@ -11,10 +11,12 @@ const svgRenderer_1 = require("../render/svgRenderer");
11
11
  const png_1 = require("../render/png");
12
12
  const binding_1 = require("../render/binding");
13
13
  const liveContext_1 = require("./liveContext");
14
+ const httpJson_1 = require("../httpJson");
14
15
  const log_1 = require("./log");
15
16
  (0, registry_1.registerDriver)(new zhsunyco_1.ZhsunycoDriver());
16
17
  const VENDOR_IDENTIFY_TIMEOUT_MS = 30000;
17
- const DEFAULT_SIGNALK_URL = 'http://localhost:3000';
18
+ /** Tried in order when -u/--url is omitted (and -e/--example-data isn't given) - first one that answers wins. */
19
+ const DEFAULT_SIGNALK_URLS = ['http://localhost', 'http://localhost:3000', 'https://localhost'];
18
20
  const COLOUR_CODES = {
19
21
  BW: ['black', 'white'],
20
22
  BWR: ['black', 'white', 'red'],
@@ -27,9 +29,26 @@ function parseColours(code) {
27
29
  }
28
30
  return colours;
29
31
  }
30
- /** Shared by every command that takes -u/--url and -e/--example-data - -e wins when both are present (-u always carries a default, so its mere presence doesn't mean it was explicitly chosen). */
31
- function assembleContext(opts, bindings) {
32
- return opts.exampleData ? (0, liveContext_1.assembleExampleContext)(opts.exampleData, bindings) : (0, liveContext_1.assembleLiveContext)(opts.url, bindings);
32
+ /** Probes DEFAULT_SIGNALK_URLS in order and returns the first that answers a plain GET - used when -u/--url is omitted. */
33
+ async function resolveDefaultUrl() {
34
+ for (const candidate of DEFAULT_SIGNALK_URLS) {
35
+ try {
36
+ (0, log_1.logDebug)(`probing ${candidate} as a default SignalK server`);
37
+ await (0, httpJson_1.fetchJson)(`${candidate}/signalk`);
38
+ return candidate;
39
+ }
40
+ catch (err) {
41
+ (0, log_1.logDebug)(`${candidate} did not answer: ${err.message}`);
42
+ }
43
+ }
44
+ throw new Error(`no -u/--url given and none of ${DEFAULT_SIGNALK_URLS.join(', ')} answered - specify the server explicitly with -u/--url`);
45
+ }
46
+ /** 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. */
47
+ async function assembleContext(opts, bindings) {
48
+ if (opts.exampleData)
49
+ return (0, liveContext_1.assembleExampleContext)(opts.exampleData, bindings);
50
+ const url = opts.url ?? (await resolveDefaultUrl());
51
+ return (0, liveContext_1.assembleLiveContext)(url, bindings);
33
52
  }
34
53
  /** Connects long enough to read the advertised name and manufacturer ID, then matches against registered drivers. */
35
54
  async function identifyVendor(address) {
@@ -92,7 +111,7 @@ program
92
111
  .command('scan')
93
112
  .description('Scan for supported BLE ESL devices across all registered vendor drivers')
94
113
  .option('-d, --duration <seconds>', 'scan duration in seconds', '10')
95
- .option('-a, --all-devices', 'list every nearby BLE device, not just ones a registered driver recognised - unmatched devices show address/name/mfr/rssi only, since there\'s no driver to do a vendor-specific read like battery')
114
+ .option('-a, --all-devices', "list every nearby BLE device, not just ones a registered driver recognised - unmatched devices show address/name/mfr/rssi only, since there's no driver to do a vendor-specific read like battery")
96
115
  .action(async (opts) => {
97
116
  const durationMs = Number(opts.duration) * 1000;
98
117
  const header = ['vendor', 'address', 'name', 'pid', 'label', 'mfr', 'battery', 'rssi'];
@@ -137,12 +156,14 @@ program
137
156
  program
138
157
  .command('paint')
139
158
  .description('Render a template against a live SignalK server and send it to a device')
140
- .option('-v, --vendor <vendor>', 'vendor driver to use - if omitted, inferred from the device\'s advertised name')
159
+ .option('-v, --vendor <vendor>', "vendor driver to use - if omitted, inferred from the device's advertised name")
141
160
  .requiredOption('-a, --address <address>', 'BLE address of the device')
142
161
  .requiredOption('-t, --template <path>', 'path to SVG template')
143
- .option('-u, --url <url>', 'SignalK server base URL - resolves the template\'s source=signalk/resources bindings', DEFAULT_SIGNALK_URL)
162
+ .option('-u, --url <url>', "SignalK server base URL - resolves the template's source=signalk/resources bindings - if omitted, tries each of " +
163
+ DEFAULT_SIGNALK_URLS.join(', ') +
164
+ ' in turn')
144
165
  .option('-e, --example-data <dir>', 'load vessels/resources from local example JSON files in <dir> (e.g. ./examples) instead of a live SignalK server - alternative to -u')
145
- .option('-k, --aes-key <hex>', 'AES-128 key for device authentication, as 32 hex characters - defaults to the vendor\'s stock key if omitted')
166
+ .option('-k, --aes-key <hex>', "AES-128 key for device authentication, as 32 hex characters - defaults to the vendor's stock key if omitted")
146
167
  .option('-w, --width <px>', 'render width', '416')
147
168
  .option('--height <px>', 'render height', '240')
148
169
  .option('--voffset <px>', 'vertical pixel offset of the panel - overrides the looked-up model for unsupported hardware (requires --colours)', '0')
@@ -175,14 +196,16 @@ program
175
196
  }
176
197
  await driver.paint(bitmap, { address: opts.address, aesKey: opts.aesKey, modelOverride, connectTimeoutMs });
177
198
  });
178
- console.log(`painted ${opts.address} (${bitmap.width}x${bitmap.height})`);
199
+ console.log(`painted ${opts.address} (${bitmap.width}x${bitmap.height}) ${opts.colours}`);
179
200
  });
180
201
  program
181
202
  .command('render')
182
203
  .description('Render a template against a live SignalK server and write a PNG, without needing a device')
183
204
  .requiredOption('-t, --template <path>', 'path to SVG template')
184
205
  .requiredOption('-o, --output <path>', 'output PNG path')
185
- .option('-u, --url <url>', 'SignalK server base URL - resolves the template\'s source=signalk/resources bindings', DEFAULT_SIGNALK_URL)
206
+ .option('-u, --url <url>', "SignalK server base URL - resolves the template's source=signalk/resources bindings - if omitted, tries each of " +
207
+ DEFAULT_SIGNALK_URLS.join(', ') +
208
+ ' in turn')
186
209
  .option('-e, --example-data <dir>', 'load vessels/resources from local example JSON files in <dir> (e.g. ./examples) instead of a live SignalK server - alternative to -u')
187
210
  .option('-w, --width <px>', 'render width', '416')
188
211
  .option('--height <px>', 'render height', '240')
@@ -199,7 +222,9 @@ program
199
222
  .command('fields')
200
223
  .description('List every <desc> binding in a template by element id, with its source spec and resolved value')
201
224
  .requiredOption('-t, --template <path>', 'path to SVG template')
202
- .option('-u, --url <url>', 'SignalK server base URL - resolves the template\'s source=signalk/resources bindings', DEFAULT_SIGNALK_URL)
225
+ .option('-u, --url <url>', "SignalK server base URL - resolves the template's source=signalk/resources bindings - if omitted, tries each of " +
226
+ DEFAULT_SIGNALK_URLS.join(', ') +
227
+ ' in turn')
203
228
  .option('-e, --example-data <dir>', 'load vessels/resources from local example JSON files in <dir> (e.g. ./examples) instead of a live SignalK server - alternative to -u')
204
229
  .action(async (opts) => {
205
230
  const doc = new xmldom_1.DOMParser().parseFromString(await (0, promises_1.readFile)(opts.template, 'utf-8'), 'image/svg+xml');
@@ -239,7 +264,9 @@ program
239
264
  .command('field')
240
265
  .description('Resolve a single binding spec directly against a live SignalK server, with no template')
241
266
  .argument('<spec>', 'binding spec, e.g. "source=resources,resource=tides,path=station.name" or a bare SignalK path')
242
- .option('-u, --url <url>', 'SignalK server base URL - resolves the spec\'s source=signalk/resources binding', DEFAULT_SIGNALK_URL)
267
+ .option('-u, --url <url>', "SignalK server base URL - resolves the spec's source=signalk/resources binding - if omitted, tries each of " +
268
+ DEFAULT_SIGNALK_URLS.join(', ') +
269
+ ' in turn')
243
270
  .option('-e, --example-data <dir>', 'load vessels/resources from local example JSON files in <dir> (e.g. ./examples) instead of a live SignalK server - alternative to -u')
244
271
  .action(async (spec, opts) => {
245
272
  const binding = (0, binding_1.parseBinding)(spec);
package/dist/config.js CHANGED
@@ -168,7 +168,13 @@ function configSchema(app, discovered = []) {
168
168
  repaintTrigger: { type: 'string', title: 'Repaint trigger', enum: ['subscription', 'interval'] },
169
169
  triggerPath: { type: 'string', title: 'Trigger SignalK path (if repaint trigger is subscription)' },
170
170
  intervalHours: { type: 'number', title: 'Repaint every N hours (if repaint trigger is interval)', minimum: 1 },
171
- intervalMinute: { type: 'number', title: 'Minutes past the hour (if repaint trigger is interval)', minimum: 0, maximum: 59, default: 0 },
171
+ intervalMinute: {
172
+ type: 'number',
173
+ title: 'Minutes past the hour (if repaint trigger is interval)',
174
+ minimum: 0,
175
+ maximum: 59,
176
+ default: 0,
177
+ },
172
178
  aesKey: { type: 'string', title: 'BLE AES key (vendor-specific; leave blank to use a default key)' },
173
179
  forceRepaint: {
174
180
  type: 'boolean',
@@ -14,7 +14,7 @@ const DEVICE_DISCOVERY_TIMEOUT_MS = 30000;
14
14
  /** Used while identifying a device during a scan - kept short since a scan may be enumerating several devices. */
15
15
  const SCAN_CONNECT_TIMEOUT_MS = 10000;
16
16
  /** Fallback when `VendorDeviceConfig.connectTimeoutMs` is omitted (e.g. a bare CLI `paint` call) - matches `defaultConfig().paintConnectTimeoutSeconds`. */
17
- const DEFAULT_PAINT_CONNECT_TIMEOUT_MS = 30000;
17
+ const DEFAULT_PAINT_CONNECT_TIMEOUT_MS = 60000;
18
18
  class ZhsunycoDriver {
19
19
  constructor() {
20
20
  this.vendor = 'zhsunyco';
@@ -65,9 +65,7 @@ class ZhsunycoDriver {
65
65
  if (!info) {
66
66
  throw new Error('zhsunyco device did not return valid config data');
67
67
  }
68
- const metadata = config.modelOverride
69
- ? { pid: info.pid, ...config.modelOverride }
70
- : this.metadataForPid(info.pid, info.hwVersion);
68
+ const metadata = config.modelOverride ? { pid: info.pid, ...config.modelOverride } : this.metadataForPid(info.pid, info.hwVersion);
71
69
  if (!metadata) {
72
70
  throw new Error(`zhsunyco device reports unrecognised PID 0x${info.pid.toString(16).padStart(4, '0')} - ` +
73
71
  'pass --width/--height/--voffset/--colours to describe it manually');
@@ -57,10 +57,7 @@ const AES_CHALLENGE_LENGTH = 16;
57
57
  * reference driver's `BLE_SECRET_KEY`). Used as a fallback when a device hasn't been
58
58
  * given its own key via the plugin config.
59
59
  */
60
- exports.DEFAULT_BLE_AUTH = [
61
- 155, 96, 159, 40, 188, 73, 226, 87,
62
- 41, 189, 123, 141, 242, 43, 68, 32,
63
- ];
60
+ exports.DEFAULT_BLE_AUTH = [155, 96, 159, 40, 188, 73, 226, 87, 41, 189, 123, 141, 242, 43, 68, 32];
64
61
  /** Resolves the configured per-device hex key, falling back to `DEFAULT_BLE_AUTH`. */
65
62
  function resolveAesKey(aesKeyHex) {
66
63
  return aesKeyHex ? Buffer.from(aesKeyHex, 'hex') : Buffer.from(exports.DEFAULT_BLE_AUTH);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-einklabel-plugin",
3
- "version": "0.6.0",
4
- "description": "SignalK plugin that renders selected SignalK data to eInk Electronic Shelf Labels",
3
+ "version": "0.6.2",
4
+ "description": "Display SignalK data on eInk Electronic Shelf Labels",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -19,11 +19,14 @@
19
19
  "build": "tsc",
20
20
  "watch": "tsc --watch",
21
21
  "cli": "ts-node src/cli/index.ts",
22
- "test": "node --require ts-node/register --test \"src/**/*.test.ts\"",
22
+ "test": "node scripts/run-tests.js",
23
+ "coverage": "node scripts/run-tests.js --coverage",
24
+ "prettier:check": "prettier --check .",
25
+ "prettier:write": "prettier --write .",
23
26
  "prepublishOnly": "npm run build"
24
27
  },
25
28
  "signalk": {
26
- "displayName": "eInk Label Instruments",
29
+ "displayName": "eInk Label Displays",
27
30
  "appIcon": "docs/assets/icons/icon_tmp.png",
28
31
  "screenshots": [
29
32
  "docs/assets/screenshots/example_tidal_clock.png"
@@ -38,7 +41,8 @@
38
41
  "esl",
39
42
  "display",
40
43
  "instrument",
41
- "ble"
44
+ "ble",
45
+ "tides"
42
46
  ],
43
47
  "engines": {
44
48
  "node": ">=18"
@@ -61,6 +65,7 @@
61
65
  "@types/luxon": "^3.7.1",
62
66
  "@types/node": "^20.14.0",
63
67
  "@types/pngjs": "^6.0.5",
68
+ "prettier": "^3.8.5",
64
69
  "ts-node": "^10.9.2",
65
70
  "typescript": "^5.5.0"
66
71
  },
@@ -269,4 +269,4 @@
269
269
  id="moon_phase"
270
270
  x="340.3974"
271
271
  y="90.153488"><desc
272
- id="desc22">path=environment.moonPhase.name,assets=../resources/svg/lunar_phases</desc></image></svg>
272
+ id="desc22">path=environment.moon.phaseName,assets=../resources/svg/lunar_phases</desc></image></svg>