@rhizomatics/signalk-einklabel-plugin 1.2.3 → 1.3.0-beta10
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 +28 -0
- package/README.md +97 -9
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.js +30 -6
- package/dist/config.d.ts +53 -14
- package/dist/config.js +172 -41
- package/dist/devices/bleBackend.d.ts +49 -0
- package/dist/devices/bleBackend.js +268 -0
- package/dist/devices/bleDiscovery.d.ts +20 -2
- package/dist/devices/bleDiscovery.js +101 -3
- package/dist/devices/discoveryCoordinator.d.ts +25 -6
- package/dist/devices/discoveryCoordinator.js +148 -9
- package/dist/devices/gattConnection.d.ts +9 -0
- package/dist/devices/gattConnection.js +2 -0
- package/dist/devices/gicisky/compression.d.ts +22 -9
- package/dist/devices/gicisky/compression.js +128 -13
- package/dist/devices/gicisky/encode.d.ts +1 -1
- package/dist/devices/gicisky/encode.js +2 -2
- package/dist/devices/gicisky/index.d.ts +7 -2
- package/dist/devices/gicisky/index.js +118 -96
- package/dist/devices/gicisky/layout.d.ts +13 -1
- package/dist/devices/gicisky/layout.js +21 -0
- package/dist/devices/types.d.ts +48 -6
- package/dist/devices/types.js +2 -0
- package/dist/devices/zhsunyco/compression.d.ts +1 -0
- package/dist/devices/zhsunyco/compression.js +30 -0
- package/dist/devices/zhsunyco/index.d.ts +8 -2
- package/dist/devices/zhsunyco/index.js +92 -82
- package/dist/devices/zhsunyco/protocol.d.ts +2 -0
- package/dist/devices/zhsunyco/protocol.js +2 -0
- package/dist/plugin.js +38 -11
- package/dist/render/mirror.d.ts +11 -0
- package/dist/render/mirror.js +24 -0
- package/dist/repaintScheduler.js +29 -12
- package/docs/assets/images/mini_tidal_clock.png +0 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
# 1.3.0
|
|
2
|
+
|
|
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
|
+
- BLE Manager use reduces interference between plugins competing for same BLE devices
|
|
10
|
+
- Recommend using SignalK at least release v2.33.0.
|
|
11
|
+
|
|
12
|
+
## Compression
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
|
|
16
|
+
- 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
|
|
17
|
+
- Experimental opt-in _Wire format_ `chunked` sends a Gicisky 4.2" BWR compressed too (`--compression-format chunked` on the CLI)
|
|
18
|
+
|
|
19
|
+
## Advanced Options
|
|
20
|
+
|
|
21
|
+
Connect timeout and retries can now be defined per label.
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
Different 'chunking' options to help with new label models.
|
|
26
|
+
|
|
27
|
+
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).
|
|
28
|
+
|
|
1
29
|
# 1.2.3
|
|
2
30
|
|
|
3
31
|
- Improved example tide template for 2.9" Gicisky, and added blank and error templates
|
package/README.md
CHANGED
|
@@ -24,22 +24,31 @@ Being battery operated, they can be stuck on anywhere without wiring - the only
|
|
|
24
24
|
|
|
25
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.
|
|
26
26
|
|
|
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.
|
|
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.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
This plugin can reach BLE hardware two ways - pick whichever fits your setup:
|
|
30
30
|
|
|
31
|
-
-
|
|
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
|
|
32
37
|
debugging (everything except `scan` and `paint`)
|
|
33
38
|
|
|
34
|
-
2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy).
|
|
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.
|
|
35
40
|
|
|
36
|
-
- Bluetooth adapters for Linux can be tricky
|
|
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
|
|
37
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.
|
|
38
47
|
|
|
39
48
|
> - Don't worry about the very latest Bluetooth versions, 4.0 is minimum for BLE, 5.0 is nice
|
|
40
49
|
> - Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
|
|
41
50
|
|
|
42
|
-
3. `bluez` package installed in Linux
|
|
51
|
+
3. `bluez` package installed in Linux - direct BlueZ mode only
|
|
43
52
|
|
|
44
53
|
- No need to do this if you have a Raspberry Pi with recent Raspian version, since bluez comes built in.
|
|
45
54
|
- If you're not running a Raspberry Pi, then ensure that the `dbus` package is installed
|
|
@@ -82,6 +91,10 @@ Use the standard configuration option in the SignalK menu for the plugin.
|
|
|
82
91
|
|
|
83
92
|
Template available as 416x240-BWRY for 3.7" ESLs and a simpler template, sized 250x128, also BWRY, for the cheapest 2.13" labels.
|
|
84
93
|
|
|
94
|
+
This mini tide clock is a 2.9" Gicisky device, less than £10 inc delivery in summer 2026.
|
|
95
|
+
|
|
96
|
+

|
|
97
|
+
|
|
85
98
|
#### Pre-requisites
|
|
86
99
|
|
|
87
100
|
A _tides_ provider plugin for the Resources API installed and enabled, currently one of:
|
|
@@ -121,10 +134,16 @@ Enable the plugin, and use the large **+** sign to add a label, which opens up t
|
|
|
121
134
|
- If it's a SignalK path, enter it next, for example `environment.tide.state`
|
|
122
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.
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
- _If the render doesn't match the panel size_ - see [Reframing](#reframing)
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
The rest are grouped under _Advanced settings_, and can usually be ignored.
|
|
140
|
+
|
|
141
|
+
- _Compress upload_, _Mirror_ and _Wire format_ - see [Other Image Options](#other-image-options)
|
|
127
142
|
- _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.)
|
|
143
|
+
- _BLE AES key_ - Only needed if the default key doesn't work and you have a better alternative, otherwise ignore
|
|
144
|
+
- _Paint connect timeout_ and _Paint retries_ for this device - leave blank to use the plugin-wide settings, or set them for a label that's slower to respond or further away than the others
|
|
145
|
+
|
|
146
|
+
Configs saved by earlier versions are moved into this layout automatically when the plugin starts.
|
|
128
147
|
|
|
129
148
|
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.
|
|
130
149
|
|
|
@@ -165,6 +184,14 @@ Templates are simply SVG files, to which expressions can be added to use SignalK
|
|
|
165
184
|
|
|
166
185
|
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.
|
|
167
186
|
|
|
187
|
+
### Other Image Options
|
|
188
|
+
|
|
189
|
+
Each label has a few more settings for how the image is sent, under _Advanced settings_. The CLI `paint` command has matching options (see [Command Line Interface](#command-line-interface)), so you can try them on a label before changing the plugin config.
|
|
190
|
+
|
|
191
|
+
- _Compress upload_ - on by default. Sends much less data over Bluetooth, so painting is quicker, uses less of the label's battery and is less likely to time out. Works for Zhsunyco labels and Gicisky 7.5"/10.2" labels, and is ignored for others. Turn it off if a label stops updating.
|
|
192
|
+
- _Wire format (Gicisky, experimental)_ - `auto` by default. `chunked` sends a Gicisky 4.2" BWR label compressed, the same way as the 7.5"/10.2". The vendor's own app has been seen doing this, but it hasn't been tested on current firmware. Set it back to `auto` if the label stops updating.
|
|
193
|
+
- _Mirror_ - `none` by default. `horizontal` or `vertical` fixes a label model whose image comes out mirrored. `both` rotates the image 180°, for a label that has to be mounted upside down.
|
|
194
|
+
|
|
168
195
|
### Template Families (multiple panel sizes/colours)
|
|
169
196
|
|
|
170
197
|
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.
|
|
@@ -296,6 +323,12 @@ Left unset, both `render` and `paint` default `-w/--width`/`--height` to the tem
|
|
|
296
323
|
|
|
297
324
|
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".
|
|
298
325
|
|
|
326
|
+
`paint` has matching options for the other per-label image settings too (see [Other Image Options](#other-image-options)):
|
|
327
|
+
|
|
328
|
+
- `--mirror <mode>` - `none` (default), `horizontal`, `vertical`, or `both` (rotate 180°). `render` also takes `--mirror`, to preview the flip as a PNG without a label
|
|
329
|
+
- `--no-compress` - send the image uncompressed, to rule compression out if a label won't update
|
|
330
|
+
- `--compression-format <format>` - `auto` (default) or `chunked`, to try the experimental compressed format on a Gicisky 4.2" BWR
|
|
331
|
+
|
|
299
332
|
`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.
|
|
300
333
|
|
|
301
334
|
( 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` )
|
|
@@ -364,12 +397,24 @@ The label address previously discovered via `esl-cli scan`
|
|
|
364
397
|
npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93
|
|
365
398
|
```
|
|
366
399
|
|
|
367
|
-
If the label turns out to be a different size than the template (e.g. it's a 250x128 template on a 416x240 panel),
|
|
400
|
+
If the label turns out to be a different size than the template (e.g. it's a 250x128 template on a 416x240 panel), it's cropped to fit by default - add `--reframe scale` to stretch it instead:
|
|
368
401
|
|
|
369
402
|
```bash
|
|
370
403
|
npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93 --reframe scale
|
|
371
404
|
```
|
|
372
405
|
|
|
406
|
+
If a label doesn't update, try sending it uncompressed to see whether compression is the cause:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93 --no-compress
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
If the image comes out mirrored, try each `--mirror` mode until it looks right, then set the same _Mirror_ option in the label's config:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
npx esl-cli paint -t templates/tides/250x128-BWRY.svg -a FF:FF:92:84:53:93 --mirror horizontal
|
|
416
|
+
```
|
|
417
|
+
|
|
373
418
|
#### Test Template Without Updating Label
|
|
374
419
|
|
|
375
420
|
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).
|
|
@@ -461,6 +506,8 @@ That's the bundled fallback warning, not necessarily an error in this plugin - i
|
|
|
461
506
|
|
|
462
507
|
### SignalK starts before the Bluetooth daemon — does the plugin need `bluetoothd` running at boot?
|
|
463
508
|
|
|
509
|
+
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.
|
|
510
|
+
|
|
464
511
|
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.
|
|
465
512
|
|
|
466
513
|
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:
|
|
@@ -486,8 +533,44 @@ sudo systemctl restart signalk
|
|
|
486
533
|
|
|
487
534
|
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.
|
|
488
535
|
|
|
536
|
+
### Bluetooth Dongle with "No gpio to reset"
|
|
537
|
+
|
|
538
|
+
Example log:
|
|
539
|
+
|
|
540
|
+
```
|
|
541
|
+
Bluetooth: hci0: No gpio to reset Realtek device, ignoring
|
|
542
|
+
Bluetooth: hci0: Unable to disable scanning: -110
|
|
543
|
+
Bluetooth: hci0: command 0x2042 tx timeout
|
|
544
|
+
Bluetooth: hci0: Opcode 0x2042 failed: -110
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
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.
|
|
548
|
+
|
|
549
|
+
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.
|
|
550
|
+
|
|
551
|
+
#### Example udev rule fix
|
|
552
|
+
|
|
553
|
+
Following file created at `/etc/udev/rules.d/99-bt500-no-autosuspend.rules`
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
# Disable USB autosuspend for the ASUS USB-BT500 (RTL8761BU, 0b05:190e).
|
|
557
|
+
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0b05", ATTR{idProduct}=="190e", TEST=="power/control", ATTR{power/control}="on"
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
If `tlp` running to minimize power, it may have its own rules trying to suspend the Bluetooth dongle.
|
|
561
|
+
|
|
562
|
+
#### Example tlp fix
|
|
563
|
+
|
|
564
|
+
Following file created at /etc/tlp.d/99-bt500-no-autosuspend.conf
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
USB_DENYLIST="0b05:190e"
|
|
568
|
+
```
|
|
569
|
+
|
|
489
570
|
## Other ESL and General eInk Resources
|
|
490
571
|
|
|
572
|
+
### Components
|
|
573
|
+
|
|
491
574
|
- [Open ePaper Link](https://openepaperlink.de) - Alternative open source firmware to flash onto eInk shelf labels, with Home Assistant integration.
|
|
492
575
|
- [zhsunyco-esl](https://github.com/roxburghm/zhsunyco-esl) - Python interface
|
|
493
576
|
- [WoLink](https://github.com/NickWaterton/Wolink) - Python interface and protocol analysis
|
|
@@ -496,9 +579,14 @@ This tells systemd to start `bluetoothd` first and wait for it before starting S
|
|
|
496
579
|
- [esp32-esl-system](https://github.com/giobauermeister/esp32-esl-system) - Docker and ESP32 based system for updating ESLs.
|
|
497
580
|
- [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.
|
|
498
581
|
- [ha-panda](https://github.com/moryoav/ha-panda) - Home Assistant integration for Panda ESLs ( a similar vendor to Zhsunyco).
|
|
582
|
+
|
|
583
|
+
### Notes and Experiences
|
|
584
|
+
|
|
585
|
+
- [Cabalist Gicisky Image Notes](https://github.com/Cabalist/gicisky_image_notes)
|
|
499
586
|
- [Dmitry.gr](https://dmitry.gr/?r=05.Projects&proj=29.%20eInk%20Price%20Tags) - Personal site of an ESL hacker
|
|
500
587
|
- [Aaron Christobel](https://www.youtube.com/@atc1441) - YouTube channel of an ESL hacker.
|
|
501
588
|
- [rbaron.net](https://rbaron.net/blog/2022/07/29/Daisy-chaining-multiple-electronic-shelf-labels) - Blog of an early ESL hacker.
|
|
589
|
+
### Retail
|
|
502
590
|
- [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.
|
|
503
591
|
- [WaveShare](https://www.waveshare.com/product/displays/e-paper.htm) - Wide range of eInk displays for hardware projects, not limited to ESLs.
|
|
504
592
|
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/cli/index.js
CHANGED
|
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.program = exports.DEFAULT_SIGNALK_URLS = void 0;
|
|
5
5
|
exports.parseColours = parseColours;
|
|
6
6
|
exports.parseReframeMode = parseReframeMode;
|
|
7
|
+
exports.parseMirrorMode = parseMirrorMode;
|
|
8
|
+
exports.parseCompressionFormat = parseCompressionFormat;
|
|
7
9
|
exports.assembleContext = assembleContext;
|
|
8
10
|
const promises_1 = require("fs/promises");
|
|
9
11
|
const path_1 = require("path");
|
|
@@ -13,6 +15,8 @@ const registry_1 = require("../devices/registry");
|
|
|
13
15
|
const zhsunyco_1 = require("../devices/zhsunyco");
|
|
14
16
|
const gicisky_1 = require("../devices/gicisky");
|
|
15
17
|
const bleDiscovery_1 = require("../devices/bleDiscovery");
|
|
18
|
+
const types_1 = require("../devices/types");
|
|
19
|
+
const mirror_1 = require("../render/mirror");
|
|
16
20
|
const svgRenderer_1 = require("../render/svgRenderer");
|
|
17
21
|
const png_1 = require("../render/png");
|
|
18
22
|
const binding_1 = require("../render/binding");
|
|
@@ -45,6 +49,18 @@ function parseReframeMode(value) {
|
|
|
45
49
|
}
|
|
46
50
|
return value;
|
|
47
51
|
}
|
|
52
|
+
function parseMirrorMode(value) {
|
|
53
|
+
if (!mirror_1.MIRROR_MODES.includes(value)) {
|
|
54
|
+
throw new Error(`unknown --mirror value "${value}" - expected one of ${mirror_1.MIRROR_MODES.join(", ")}`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function parseCompressionFormat(value) {
|
|
59
|
+
if (!types_1.COMPRESSION_FORMATS.includes(value)) {
|
|
60
|
+
throw new Error(`unknown --compression-format value "${value}" - expected one of ${types_1.COMPRESSION_FORMATS.join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
48
64
|
/** Probes DEFAULT_SIGNALK_URLS in order and returns the first that answers a plain GET - used when -u/--url is omitted. */
|
|
49
65
|
async function resolveDefaultUrl() {
|
|
50
66
|
for (const candidate of exports.DEFAULT_SIGNALK_URLS) {
|
|
@@ -179,18 +195,18 @@ exports.program
|
|
|
179
195
|
await (0, bleDiscovery_1.forEachAdvertisedDevice)(adapter, async ({ device, address, name, manufacturerId, manufacturerData }) => {
|
|
180
196
|
const driver = drivers.find((candidate) => candidate.matchesAdvertisement(name, manufacturerId));
|
|
181
197
|
const mfr = manufacturerId !== undefined ? `0x${manufacturerId.toString(16).padStart(4, "0")}` : "";
|
|
198
|
+
const rssi = await device
|
|
199
|
+
.getRSSI()
|
|
200
|
+
.then((value) => (value === undefined ? undefined : Number(value)))
|
|
201
|
+
.catch(() => undefined);
|
|
182
202
|
if (!driver) {
|
|
183
203
|
if (opts.allDevices) {
|
|
184
|
-
const rssi = await device
|
|
185
|
-
.getRSSI()
|
|
186
|
-
.then((value) => (value === undefined ? undefined : Number(value)))
|
|
187
|
-
.catch(() => undefined);
|
|
188
204
|
rows.push(["(unmatched)", address, name ?? "", "", "", "", mfr, "", String(rssi ?? "")]);
|
|
189
205
|
}
|
|
190
206
|
return;
|
|
191
207
|
}
|
|
192
208
|
matchedCount++;
|
|
193
|
-
const found = await driver.identifyDevice(
|
|
209
|
+
const found = await driver.identifyDevice({ address, name, manufacturerId, manufacturerData, rssi }, () => (0, bleDiscovery_1.connectWithTimeout)(device, VENDOR_IDENTIFY_TIMEOUT_MS).then(() => (0, bleDiscovery_1.openNodeBleGattConnection)(device)));
|
|
194
210
|
(0, log_1.logDebug)(`${driver.vendor}: identified ${found.name ?? found.address}`);
|
|
195
211
|
const pid = found.pid !== undefined ? `0x${found.pid.toString(16).padStart(4, "0")}` : "";
|
|
196
212
|
const hwid = found.hwVersion ? `0x${found.hwVersion}` : "";
|
|
@@ -226,6 +242,9 @@ exports.program
|
|
|
226
242
|
.option("--voffset <px>", "vertical pixel offset of the panel - overrides the looked-up model for unsupported hardware (requires --colours)", "0")
|
|
227
243
|
.option("--colours <code>", "device colour palette for unsupported hardware: BW, BWR, or BWRY - overrides the looked-up model (uses --width/--height/--voffset)")
|
|
228
244
|
.option("--reframe <mode>", "how to fit the rendered image onto the device's actual panel size when it doesn't match: crop (default - place at top-left, truncating or leaving the rest blank), scale (stretch the template to the panel), fixed (reject the mismatch instead)", "crop")
|
|
245
|
+
.option("--mirror <mode>", "flip the image before sending: none (default), horizontal, vertical, or both (rotate 180°)", "none")
|
|
246
|
+
.option("--no-compress", 'send the image uncompressed (zhsunyco; gicisky 7.5"/10.2" or --compression-format chunked - compression is on by default)')
|
|
247
|
+
.option("--compression-format <format>", 'gicisky wire format: auto (default - the model\'s usual format) or chunked (experimental - send a 4.2" BWR compressed like the 7.5"/10.2")', "auto")
|
|
229
248
|
.option("--connect-timeout <seconds>", "BLE connect timeout before giving up on an attempt", "30")
|
|
230
249
|
.option("--retries <n>", "number of paint attempts (including the first) before giving up", "3")
|
|
231
250
|
.action(async (opts) => {
|
|
@@ -269,6 +288,9 @@ exports.program
|
|
|
269
288
|
modelOverride,
|
|
270
289
|
connectTimeoutMs,
|
|
271
290
|
reframe: parseReframeMode(opts.reframe),
|
|
291
|
+
mirror: parseMirrorMode(opts.mirror),
|
|
292
|
+
compress: opts.compress,
|
|
293
|
+
compressionFormat: parseCompressionFormat(opts.compressionFormat),
|
|
272
294
|
});
|
|
273
295
|
});
|
|
274
296
|
console.log(`painted ${opts.address} (${bitmap.width}x${bitmap.height}) ${opts.colours}`);
|
|
@@ -287,7 +309,9 @@ exports.program
|
|
|
287
309
|
.option("-f, --font <path>", "override a bundled font with this file (repeatable) - defaults to the bundled monospace/sans-serif/serif trio",
|
|
288
310
|
// See the -r/--require option above for why `| undefined` is needed here despite commander's typings.
|
|
289
311
|
(value, previous = []) => [...previous, value])
|
|
312
|
+
.option("--mirror <mode>", "flip the PNG the same way paint --mirror would: none (default), horizontal, vertical, or both", "none")
|
|
290
313
|
.action(async (opts) => {
|
|
314
|
+
const mirror = parseMirrorMode(opts.mirror);
|
|
291
315
|
const svgSource = await (0, promises_1.readFile)(opts.template, "utf-8");
|
|
292
316
|
const declared = (0, binding_1.readTemplateDimensions)(svgSource);
|
|
293
317
|
const width = resolveDimension(opts.width, [declared.width], DEFAULT_RENDER_WIDTH);
|
|
@@ -295,7 +319,7 @@ exports.program
|
|
|
295
319
|
const bindings = (0, binding_1.findBindings)(svgSource);
|
|
296
320
|
const context = await assembleContext(opts, bindings);
|
|
297
321
|
const renderer = opts.font ? new svgRenderer_1.SvgRenderer(opts.font) : new svgRenderer_1.SvgRenderer();
|
|
298
|
-
const bitmap = await renderer.render(opts.template, context, width, height, (0, path_1.dirname)(opts.template), config_1.BUNDLED_TEMPLATES_DIR);
|
|
322
|
+
const bitmap = (0, mirror_1.mirrorBitmap)(await renderer.render(opts.template, context, width, height, (0, path_1.dirname)(opts.template), config_1.BUNDLED_TEMPLATES_DIR), mirror);
|
|
299
323
|
await (0, promises_1.writeFile)(opts.output, (0, png_1.bitmapToPng)(bitmap));
|
|
300
324
|
console.log(`wrote ${opts.output} (${bitmap.width}x${bitmap.height})`);
|
|
301
325
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ServerAPI } from "@signalk/server-api";
|
|
2
|
-
import { Colour, DiscoveredDevice } from "./devices/types";
|
|
2
|
+
import { Colour, CompressionFormat, DiscoveredDevice } from "./devices/types";
|
|
3
3
|
import { ReframeMode } from "./render/reframe";
|
|
4
|
+
import { MirrorMode } from "./render/mirror";
|
|
4
5
|
/**
|
|
5
6
|
* Special `device` value meaning "every currently-known discovered device" instead of one specific
|
|
6
7
|
* BLE address - lets a single `DeviceConfig` entry (one template, one trigger) broadcast to every
|
|
@@ -26,8 +27,6 @@ export interface DeviceConfig {
|
|
|
26
27
|
* `./render/templateProviders.ts`) tailoring content to where the label actually sits.
|
|
27
28
|
*/
|
|
28
29
|
description?: string;
|
|
29
|
-
/** Per-device override; if omitted, the vendor driver may fall back to a stock/manufacturer-default key. */
|
|
30
|
-
aesKey?: string;
|
|
31
30
|
/**
|
|
32
31
|
* Either one specific `.svg` file, or the name of a *template-family* directory holding several
|
|
33
32
|
* same-purpose templates for different panel sizes/colour-sets, named `<width>x<height>-<colours>.svg`
|
|
@@ -48,8 +47,6 @@ export interface DeviceConfig {
|
|
|
48
47
|
intervalHours?: number;
|
|
49
48
|
/** ...at this minute past the hour. */
|
|
50
49
|
intervalMinute?: number;
|
|
51
|
-
/** One-shot override to repaint even if the data is unchanged; cleared automatically once that repaint completes. */
|
|
52
|
-
forceRepaint?: boolean;
|
|
53
50
|
/**
|
|
54
51
|
* How to fit the rendered image onto the device's actual panel size when it doesn't match (see
|
|
55
52
|
* `ReframeMode`) - e.g. a template family with no variant sized for this particular label. Left
|
|
@@ -58,7 +55,34 @@ export interface DeviceConfig {
|
|
|
58
55
|
* showing *something*, even off-size, beats a repaint that just fails outright.
|
|
59
56
|
*/
|
|
60
57
|
reframe?: ReframeMode;
|
|
58
|
+
/** Settings most labels never need, grouped so the admin UI shows them in their own "Advanced settings" box. */
|
|
59
|
+
advanced?: AdvancedDeviceSettings;
|
|
61
60
|
}
|
|
61
|
+
export interface AdvancedDeviceSettings {
|
|
62
|
+
/** Compress the upload (zhsunyco, and gicisky's chunked 7.5"/10.2" panels - ignored otherwise). Unset means on; turn off if a device fails to show compressed images. */
|
|
63
|
+
compress?: boolean;
|
|
64
|
+
/** Flip the image before sending - for a panel whose layout is mirrored, or one mounted upside down (`"both"`). Unset means `"none"`. */
|
|
65
|
+
mirror?: MirrorMode;
|
|
66
|
+
/**
|
|
67
|
+
* Experimental opt-in wire format (gicisky only) - `"chunked"` sends a 4.2" BWR (or another plain
|
|
68
|
+
* two-plane panel) QuickLZ-compressed like the 7.5"/10.2". Unset means `"auto"`, the model's own
|
|
69
|
+
* format. See `CompressionFormat`.
|
|
70
|
+
*/
|
|
71
|
+
compressionFormat?: CompressionFormat;
|
|
72
|
+
/** One-shot override to repaint even if the data is unchanged; cleared automatically once that repaint completes. */
|
|
73
|
+
forceRepaint?: boolean;
|
|
74
|
+
/** Per-device override; if omitted, the vendor driver may fall back to a stock/manufacturer-default key. */
|
|
75
|
+
aesKey?: string;
|
|
76
|
+
/** Per-device override of `PluginConfig.paintConnectTimeoutSeconds` - unset uses the plugin-wide value. */
|
|
77
|
+
paintConnectTimeoutSeconds?: number;
|
|
78
|
+
/** Per-device override of `PluginConfig.paintRetries` - unset uses the plugin-wide value. */
|
|
79
|
+
paintRetries?: number;
|
|
80
|
+
}
|
|
81
|
+
/** Applies `migrateDeviceConfig` to every device entry - see `healStoredConfig` for persisting the result. */
|
|
82
|
+
export declare function migrateConfig<T extends Partial<PluginConfig>>(config: T): {
|
|
83
|
+
config: T;
|
|
84
|
+
migrated: boolean;
|
|
85
|
+
};
|
|
62
86
|
export interface PluginConfig {
|
|
63
87
|
/**
|
|
64
88
|
* Directory the plugin scans for template files, instead of an upload UI - follows
|
|
@@ -71,10 +95,20 @@ export interface PluginConfig {
|
|
|
71
95
|
* signalk-bluetti-plugin does. Off by default - a `device: ALL_DEVICES` entry scans on demand the
|
|
72
96
|
* first time it has nothing discovered yet (see `resolveTargets` in `repaintScheduler.ts`), and an
|
|
73
97
|
* explicit device selection only ever needed this to populate the dropdown once at initial setup.
|
|
98
|
+
* Also unnecessary with `useBleApi` on, which discovers continuously in the background instead (see
|
|
99
|
+
* `startBleApiDiscoveryListener` in `discoveryCoordinator.ts`) - kept meaningful mainly for direct
|
|
100
|
+
* BlueZ access, which has no continuous-scan equivalent to piggyback on.
|
|
74
101
|
*/
|
|
75
102
|
scanOnStart: boolean;
|
|
76
103
|
/** How long the startup scan runs, in seconds. */
|
|
77
104
|
scanDurationSeconds: number;
|
|
105
|
+
/**
|
|
106
|
+
* Route BLE access through the SignalK server's BLE Manager API (`app.bleApi`, server >= 2.32.0)
|
|
107
|
+
* instead of connecting to BlueZ directly, so this plugin shares the adapter with other BLE plugins
|
|
108
|
+
* instead of contending for it - see `bleBackend.ts`. Off by default, and only ever offered in the
|
|
109
|
+
* config schema when the running server actually has `app.bleApi` (see `configSchema` below).
|
|
110
|
+
*/
|
|
111
|
+
useBleApi: boolean;
|
|
78
112
|
/** How long to wait for a device to accept a BLE connection before giving up on a repaint attempt, in seconds. */
|
|
79
113
|
paintConnectTimeoutSeconds: number;
|
|
80
114
|
/** How many times to attempt a repaint (including the first try) before giving up and reporting failure. */
|
|
@@ -130,16 +164,21 @@ export declare const RENDER_FALLBACK_TEMPLATE_NAME = ".error";
|
|
|
130
164
|
export declare function defaultConfig(): PluginConfig;
|
|
131
165
|
export declare function readCurrentConfig(app: ServerAPI): Partial<PluginConfig>;
|
|
132
166
|
/**
|
|
133
|
-
* Actively rewrites the on-disk file
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* `
|
|
139
|
-
*
|
|
140
|
-
*
|
|
167
|
+
* Actively rewrites the on-disk file when it's in an outdated shape - `readCurrentConfig` alone only
|
|
168
|
+
* fixes things up in memory for callers that go through it, but the admin UI's config form
|
|
169
|
+
* round-trips whatever raw JSON it was handed verbatim. Two shapes are healed:
|
|
170
|
+
*
|
|
171
|
+
* - A nested file (see `readCurrentConfig`'s doc comment): left alone, the UI keeps re-persisting a
|
|
172
|
+
* stray nested `configuration` key it never touches (no schema field maps to it) forever (see
|
|
173
|
+
* `support/signalk-einklabel-plugin.json`).
|
|
174
|
+
* - Advanced device settings saved before they were grouped under `advanced` (see
|
|
175
|
+
* `migrateDeviceConfig`): left alone, the UI would show that group empty, even though the plugin
|
|
176
|
+
* itself still honours the old values.
|
|
177
|
+
*
|
|
178
|
+
* Called once at plugin start, which - unlike `clearForceRepaint` - isn't gated on any device having
|
|
179
|
+
* `forceRepaint` set, so an outdated file gets fixed even if nothing ever triggers that path.
|
|
141
180
|
*/
|
|
142
|
-
export declare function
|
|
181
|
+
export declare function healStoredConfig(app: ServerAPI): void;
|
|
143
182
|
/** See `resolveDir` - `templatesDir`'s own resolution. */
|
|
144
183
|
export declare function resolveTemplatesDir(templatesDir: string | undefined): string;
|
|
145
184
|
export declare function parseDevice(device: string): {
|