@rhizomatics/signalk-einklabel-plugin 1.3.0-beta10 → 1.3.0-beta12
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 +10 -0
- package/README.md +16 -3
- package/dist/config.d.ts +5 -5
- package/dist/config.js +98 -33
- package/dist/repaintScheduler.js +23 -17
- package/dist/resolveApiUrl.js +3 -0
- package/package.json +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -16,16 +16,26 @@ Images are now compressed before sending - no change to what's displayed, but se
|
|
|
16
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
17
|
- Experimental opt-in _Wire format_ `chunked` sends a Gicisky 4.2" BWR compressed too (`--compression-format chunked` on the CLI)
|
|
18
18
|
|
|
19
|
+
## Templates
|
|
20
|
+
|
|
21
|
+
- Simplified use of `label` and `einklabel` as a source of template fields, now only `label` needed, though `einklabel` won't break existing templates
|
|
22
|
+
|
|
19
23
|
## Advanced Options
|
|
20
24
|
|
|
21
25
|
Connect timeout and retries can now be defined per label.
|
|
22
26
|
|
|
27
|
+
SignalK Base URL now accepts a free-text value
|
|
28
|
+
|
|
23
29
|
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
30
|
|
|
25
31
|
Different 'chunking' options to help with new label models.
|
|
26
32
|
|
|
27
33
|
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
34
|
|
|
35
|
+
## Dependencies
|
|
36
|
+
|
|
37
|
+
- `xmldom` updated to fix security issue with markup injection
|
|
38
|
+
|
|
29
39
|
# 1.2.3
|
|
30
40
|
|
|
31
41
|
- Improved example tide template for 2.9" Gicisky, and added blank and error templates
|
package/README.md
CHANGED
|
@@ -129,15 +129,14 @@ Enable the plugin, and use the large **+** sign to add a label, which opens up t
|
|
|
129
129
|
- _Friendly Name_ - Give the label any name (word or phrase) you like, for example 'Tide Clock'
|
|
130
130
|
- _Device_ - Unless you have multiple labels, don't bother with pre-scanning or selecting a specific device, instead pick **"All discovered devices"** and it will paint any compatible labels it finds. If you want to pick a specific device, you'll need to wait for a device scan to complete.
|
|
131
131
|
- _Template_ - Choose a built-in template, one you've added to the local templates directory, or - if a companion plugin like [`@rhizomatics/signalk-einklabel-genai-plugin`](#genai-rendering) is installed - one of its own contributed entries (shown with a suffix, e.g. "forecast (GenAI)")
|
|
132
|
-
- _Location/description_ - Optional free-text notes on where this label is physically mounted/viewed from, e.g. "chart table, viewed from ~1m in poor light" - available to any template as `source=
|
|
132
|
+
- _Location/description_ - Optional free-text notes on where this label is physically mounted/viewed from, e.g. "chart table, viewed from ~1m in poor light" - available to any template as `source=label,path=description` (see [Label Details](#label-details))
|
|
133
133
|
- _Repaint Trigger_- Do you want this to repaint every few hours (at a chosen minutes past hour), or when a SignalK path changes?
|
|
134
134
|
- If it's a SignalK path, enter it next, for example `environment.tide.state`
|
|
135
135
|
- If it's time based, enter how many hours between repaints, for example 00:00/08:00/16:00 for an 8h schedule, and if you want a specific number of minutes after the hour.
|
|
136
136
|
|
|
137
|
-
- _If the render doesn't match the panel size_ - see [Reframing](#reframing)
|
|
138
|
-
|
|
139
137
|
The rest are grouped under _Advanced settings_, and can usually be ignored.
|
|
140
138
|
|
|
139
|
+
- _If the render doesn't match the panel size_ - see [Reframing](#reframing)
|
|
141
140
|
- _Compress upload_, _Mirror_ and _Wire format_ - see [Other Image Options](#other-image-options)
|
|
142
141
|
- _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
142
|
- _BLE AES key_ - Only needed if the default key doesn't work and you have a better alternative, otherwise ignore
|
|
@@ -226,6 +225,20 @@ For example, `source=resources,resource=tides,provider=tides,path=station.name`
|
|
|
226
225
|
- `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.
|
|
227
226
|
- `path=plugin_version` - expose the version of the eInk Label plugin itself.
|
|
228
227
|
|
|
228
|
+
#### Label Details
|
|
229
|
+
|
|
230
|
+
`source=label` reads facts about the label being painted, so one template can adapt to different labels. Available paths:
|
|
231
|
+
|
|
232
|
+
- `path=description` - the label's _Location/description_ setting, e.g. `source=label,path=description`
|
|
233
|
+
- `path=manufacturer` - the label's maker, e.g. `Zhsunyco`
|
|
234
|
+
- `path=label` - the model's panel size, e.g. `3.7"`
|
|
235
|
+
- `path=width` and `path=height` - the panel size in pixels
|
|
236
|
+
- `path=colours` - the colours the panel can show, e.g. `black (#000000)`; add `format=csv` for a plain comma-separated list
|
|
237
|
+
- `path=fonts` - the font families that are always available: `serif`, `sans-serif` and `monospace`
|
|
238
|
+
- `path=position` - the vessel's position, rounded to about 1km
|
|
239
|
+
|
|
240
|
+
These also work in the fallback warning shown when a template fails to render. Changing a label's description repaints it.
|
|
241
|
+
|
|
229
242
|
#### Customizing Output
|
|
230
243
|
|
|
231
244
|
A `format` can be specified to make the value easier to understand. The supported formats are:
|
package/dist/config.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface DeviceConfig {
|
|
|
22
22
|
/**
|
|
23
23
|
* Free-text notes about where this label is physically mounted/viewed from, e.g. "chart table,
|
|
24
24
|
* viewed from ~1m in poor light" - purely descriptive. Available to any template via
|
|
25
|
-
* `source=
|
|
25
|
+
* `source=label,path=description` (see `buildLabelContext` in
|
|
26
26
|
* `./render/binding.ts`) if it wants it - e.g. useful to a `TemplateProvider` extension (see
|
|
27
27
|
* `./render/templateProviders.ts`) tailoring content to where the label actually sits.
|
|
28
28
|
*/
|
|
@@ -47,6 +47,10 @@ export interface DeviceConfig {
|
|
|
47
47
|
intervalHours?: number;
|
|
48
48
|
/** ...at this minute past the hour. */
|
|
49
49
|
intervalMinute?: number;
|
|
50
|
+
/** Settings most labels never need, grouped so the admin UI shows them in their own "Advanced settings" box. */
|
|
51
|
+
advanced?: AdvancedDeviceSettings;
|
|
52
|
+
}
|
|
53
|
+
export interface AdvancedDeviceSettings {
|
|
50
54
|
/**
|
|
51
55
|
* How to fit the rendered image onto the device's actual panel size when it doesn't match (see
|
|
52
56
|
* `ReframeMode`) - e.g. a template family with no variant sized for this particular label. Left
|
|
@@ -55,10 +59,6 @@ export interface DeviceConfig {
|
|
|
55
59
|
* showing *something*, even off-size, beats a repaint that just fails outright.
|
|
56
60
|
*/
|
|
57
61
|
reframe?: ReframeMode;
|
|
58
|
-
/** Settings most labels never need, grouped so the admin UI shows them in their own "Advanced settings" box. */
|
|
59
|
-
advanced?: AdvancedDeviceSettings;
|
|
60
|
-
}
|
|
61
|
-
export interface AdvancedDeviceSettings {
|
|
62
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
63
|
compress?: boolean;
|
|
64
64
|
/** Flip the image before sending - for a panel whose layout is mirrored, or one mounted upside down (`"both"`). Unset means `"none"`. */
|
package/dist/config.js
CHANGED
|
@@ -29,6 +29,7 @@ exports.ALL_DEVICES = "ALL";
|
|
|
29
29
|
* saved before they were grouped still has them there. See `migrateDeviceConfig`.
|
|
30
30
|
*/
|
|
31
31
|
const ADVANCED_DEVICE_KEYS = [
|
|
32
|
+
"reframe",
|
|
32
33
|
"compress",
|
|
33
34
|
"mirror",
|
|
34
35
|
"compressionFormat",
|
|
@@ -359,6 +360,12 @@ function withEnum(schema, values, names) {
|
|
|
359
360
|
return schema;
|
|
360
361
|
return names ? { ...schema, oneOf: values.map((value, i) => ({ const: value, title: names[i] ?? value })) } : { ...schema, enum: values };
|
|
361
362
|
}
|
|
363
|
+
/** The plugin's documentation site - its sections' anchors are the README's own headings (see `site/scripts/sync-readme.mjs`). */
|
|
364
|
+
const DOCS_URL = "https://signalk-einklabel.rhizomatics.org.uk/";
|
|
365
|
+
/** A Markdown link to a docs section, for a field description rendered with `ui:enableMarkdownInDescription` (see `configUiSchema`). */
|
|
366
|
+
function docsLink(anchor, text = "More in the docs") {
|
|
367
|
+
return `[${text}](${DOCS_URL}#${anchor})`;
|
|
368
|
+
}
|
|
362
369
|
/**
|
|
363
370
|
* An enum-like string field whose options show explanatory labels rather than their raw stored values
|
|
364
371
|
* - `oneOf` with `const`/`title`, which RJSF 5 (the admin UI's form library) renders as each option's
|
|
@@ -447,8 +454,12 @@ function configSchema(app, discovered = []) {
|
|
|
447
454
|
signalkApiUrl: {
|
|
448
455
|
type: "string",
|
|
449
456
|
title: "SignalK API base URL (leave blank to auto-detect)",
|
|
450
|
-
description: "Used for plugin access to SignalK REST APIs not yet integrated for direct plugin access. Left blank, the plugin probes the likely options at startup (3000, 80, 443
|
|
451
|
-
|
|
457
|
+
description: "Used for plugin access to SignalK REST APIs not yet integrated for direct plugin access. Left blank, the plugin probes the likely options at startup (3000, 80, 443) - " +
|
|
458
|
+
"only set this to skip probing, or for a server on another port or host, e.g. http://localhost:3001. Anonymous read access is required.",
|
|
459
|
+
// Free text with the probed options as suggestions - `examples` renders as the input's
|
|
460
|
+
// autocomplete list in the admin UI's form library (RJSF 5), where `enum` would forbid anything else.
|
|
461
|
+
examples: resolveApiUrl_1.SIGNALK_API_URL_OPTIONS,
|
|
462
|
+
pattern: "^(\\s*|\\s*https?://\\S+\\s*)$",
|
|
452
463
|
},
|
|
453
464
|
devices: {
|
|
454
465
|
type: "array",
|
|
@@ -469,43 +480,32 @@ function configSchema(app, discovered = []) {
|
|
|
469
480
|
type: "string",
|
|
470
481
|
title: "Location/description (optional)",
|
|
471
482
|
description: 'Free-text notes about where this label is physically mounted/viewed from, e.g. "chart table, viewed from ~1m ' +
|
|
472
|
-
'in poor light" - available to any template as source=
|
|
483
|
+
'in poor light" - available to any template as `source=label,path=description`. ' +
|
|
484
|
+
docsLink("label-details"),
|
|
473
485
|
},
|
|
474
|
-
templateName: withEnum({
|
|
486
|
+
templateName: withEnum({
|
|
487
|
+
type: "string",
|
|
488
|
+
title: "Template",
|
|
489
|
+
description: `A bundled template, or one from your templates directory. ${docsLink("templating")}`,
|
|
490
|
+
}, templateNameOptions(resolveTemplatesDir(current.templatesDir))),
|
|
475
491
|
repaintTrigger: choiceField("Repaint trigger", [
|
|
476
492
|
["subscription", "When a SignalK path changes"],
|
|
477
493
|
["interval", "On a timed schedule"],
|
|
478
494
|
]),
|
|
479
|
-
triggerPath: {
|
|
480
|
-
type: "string",
|
|
481
|
-
title: "Trigger SignalK path (if repaint trigger is subscription)",
|
|
482
|
-
},
|
|
483
|
-
intervalHours: {
|
|
484
|
-
type: "number",
|
|
485
|
-
title: "Repaint every N hours (if repaint trigger is interval)",
|
|
486
|
-
minimum: 1,
|
|
487
|
-
},
|
|
488
|
-
intervalMinute: {
|
|
489
|
-
type: "number",
|
|
490
|
-
title: "Minutes past the hour (if repaint trigger is interval)",
|
|
491
|
-
minimum: 0,
|
|
492
|
-
maximum: 59,
|
|
493
|
-
default: 0,
|
|
494
|
-
},
|
|
495
|
-
reframe: choiceField("If the render doesn't match the panel size", [
|
|
496
|
-
["crop", "Crop - place at the top-left, cutting off anything too big or leaving the rest blank"],
|
|
497
|
-
["scale", "Scale - stretch to fit exactly (may distort)"],
|
|
498
|
-
["fixed", "Fixed - fail the repaint rather than show an off-size image"],
|
|
499
|
-
], { default: "crop" }),
|
|
500
495
|
advanced: {
|
|
501
496
|
type: "object",
|
|
502
497
|
title: "Advanced settings",
|
|
503
498
|
description: "Most labels never need these.",
|
|
504
499
|
properties: {
|
|
500
|
+
reframe: choiceField("If the render doesn't match the panel size", [
|
|
501
|
+
["crop", "Crop - place at the top-left, cutting off anything too big or leaving the rest blank"],
|
|
502
|
+
["scale", "Scale - stretch to fit exactly (may distort)"],
|
|
503
|
+
["fixed", "Fixed - fail the repaint rather than show an off-size image"],
|
|
504
|
+
], { description: docsLink("reframing"), default: "crop" }),
|
|
505
505
|
compress: {
|
|
506
506
|
type: "boolean",
|
|
507
507
|
title: 'Compress upload (Zhsunyco, Gicisky 7.5"/10.2")',
|
|
508
|
-
description:
|
|
508
|
+
description: `Sends far less data over BLE, so repaints are quicker. Turn off if a label stops updating. ${docsLink("other-image-options")}`,
|
|
509
509
|
default: true,
|
|
510
510
|
},
|
|
511
511
|
mirror: choiceField("Mirror", [
|
|
@@ -513,14 +513,20 @@ function configSchema(app, discovered = []) {
|
|
|
513
513
|
["horizontal", "Flip left to right"],
|
|
514
514
|
["vertical", "Flip top to bottom"],
|
|
515
515
|
["both", "Rotate 180° - for a label mounted upside down"],
|
|
516
|
-
], {
|
|
516
|
+
], {
|
|
517
|
+
description: `Only needed if the image shows up mirrored or upside down on the label. ${docsLink("other-image-options")}`,
|
|
518
|
+
default: "none",
|
|
519
|
+
}),
|
|
517
520
|
compressionFormat: choiceField("Wire format (Gicisky, experimental)", [
|
|
518
521
|
["auto", "Auto - the model's usual format"],
|
|
519
522
|
[
|
|
520
523
|
"chunked",
|
|
521
524
|
'Chunked - send compressed like the 7.5"/10.2" panels, e.g. to speed up a 4.2" BWR (untested on current firmware)',
|
|
522
525
|
],
|
|
523
|
-
], {
|
|
526
|
+
], {
|
|
527
|
+
description: `Chunked needs Compress upload on. Switch back to Auto if the label stops updating. ${docsLink("other-image-options")}`,
|
|
528
|
+
default: "auto",
|
|
529
|
+
}),
|
|
524
530
|
forceRepaint: {
|
|
525
531
|
type: "boolean",
|
|
526
532
|
title: "Force repaint",
|
|
@@ -529,7 +535,9 @@ function configSchema(app, discovered = []) {
|
|
|
529
535
|
},
|
|
530
536
|
aesKey: {
|
|
531
537
|
type: "string",
|
|
532
|
-
title: "BLE AES key (
|
|
538
|
+
title: "BLE AES key (Zhsunyco)",
|
|
539
|
+
description: "32 hex characters. Leave blank to use the default key, which works for most labels.",
|
|
540
|
+
pattern: "^([0-9a-fA-F]{32})?$",
|
|
533
541
|
},
|
|
534
542
|
paintConnectTimeoutSeconds: {
|
|
535
543
|
type: "number",
|
|
@@ -546,21 +554,78 @@ function configSchema(app, discovered = []) {
|
|
|
546
554
|
},
|
|
547
555
|
},
|
|
548
556
|
},
|
|
557
|
+
// Shows only the fields for the chosen trigger. RJSF keeps a hidden field's value, so switching
|
|
558
|
+
// trigger and back doesn't lose it - and the scheduler only reads the ones matching the trigger.
|
|
559
|
+
dependencies: {
|
|
560
|
+
repaintTrigger: {
|
|
561
|
+
oneOf: [
|
|
562
|
+
{
|
|
563
|
+
properties: {
|
|
564
|
+
repaintTrigger: { const: "subscription" },
|
|
565
|
+
triggerPath: {
|
|
566
|
+
type: "string",
|
|
567
|
+
title: "Trigger SignalK path",
|
|
568
|
+
description: "Repaints whenever this path's value changes.",
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
properties: {
|
|
574
|
+
repaintTrigger: { const: "interval" },
|
|
575
|
+
intervalHours: {
|
|
576
|
+
type: "number",
|
|
577
|
+
title: "Repaint every N hours",
|
|
578
|
+
minimum: 1,
|
|
579
|
+
},
|
|
580
|
+
intervalMinute: {
|
|
581
|
+
type: "number",
|
|
582
|
+
title: "Minutes past the hour",
|
|
583
|
+
minimum: 0,
|
|
584
|
+
maximum: 59,
|
|
585
|
+
default: 0,
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
549
592
|
},
|
|
550
593
|
},
|
|
551
594
|
},
|
|
552
595
|
};
|
|
553
596
|
}
|
|
597
|
+
/** Lets a field's `description` include Markdown - used for its `docsLink`. */
|
|
598
|
+
const MARKDOWN = { "ui:enableMarkdownInDescription": true };
|
|
554
599
|
function configUiSchema() {
|
|
555
600
|
return {
|
|
601
|
+
signalkApiUrl: { "ui:placeholder": "Auto-detect, or e.g. http://localhost:3001" },
|
|
556
602
|
devices: {
|
|
557
603
|
items: {
|
|
558
|
-
|
|
604
|
+
// Keeps the trigger's own fields (added by the schema's `dependencies`) next to it, rather than
|
|
605
|
+
// after every other field, and Advanced settings last.
|
|
606
|
+
"ui:order": [
|
|
607
|
+
"friendlyName",
|
|
608
|
+
"device",
|
|
609
|
+
"description",
|
|
610
|
+
"templateName",
|
|
611
|
+
"repaintTrigger",
|
|
612
|
+
"triggerPath",
|
|
613
|
+
"intervalHours",
|
|
614
|
+
"intervalMinute",
|
|
615
|
+
"*",
|
|
616
|
+
"advanced",
|
|
617
|
+
],
|
|
618
|
+
friendlyName: { "ui:placeholder": "e.g. Tide clock" },
|
|
619
|
+
description: { "ui:widget": "textarea", "ui:placeholder": "e.g. chart table, viewed from about 1m in poor light", ...MARKDOWN },
|
|
620
|
+
templateName: MARKDOWN,
|
|
559
621
|
repaintTrigger: { "ui:widget": "radio" },
|
|
560
|
-
|
|
622
|
+
triggerPath: { "ui:placeholder": "e.g. environment.tide.state" },
|
|
561
623
|
advanced: {
|
|
562
|
-
|
|
563
|
-
|
|
624
|
+
reframe: { "ui:widget": "radio", ...MARKDOWN },
|
|
625
|
+
compress: MARKDOWN,
|
|
626
|
+
mirror: { "ui:widget": "radio", ...MARKDOWN },
|
|
627
|
+
compressionFormat: { "ui:widget": "radio", ...MARKDOWN },
|
|
628
|
+
aesKey: { "ui:placeholder": "e.g. 00112233445566778899aabbccddeeff" },
|
|
564
629
|
},
|
|
565
630
|
},
|
|
566
631
|
},
|
package/dist/repaintScheduler.js
CHANGED
|
@@ -260,6 +260,23 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
260
260
|
let dataHash = "";
|
|
261
261
|
let paintDurationMs = 0;
|
|
262
262
|
let repaintReason = "render failed";
|
|
263
|
+
// Built up front, outside the try below, so the fallback warning render gets it too - it's all local
|
|
264
|
+
// facts (no network or template involved), so there's nothing here that can fail a render.
|
|
265
|
+
const rawPosition = (0, unwrapSignalkTree_1.unwrapSignalkTree)(app.getSelfPath("navigation.position"));
|
|
266
|
+
const position = typeof rawPosition?.latitude === "number" && typeof rawPosition?.longitude === "number"
|
|
267
|
+
? { latitude: rawPosition.latitude, longitude: rawPosition.longitude }
|
|
268
|
+
: undefined;
|
|
269
|
+
const labelContext = (0, binding_1.buildLabelContext)({
|
|
270
|
+
// Falls back to the driver's own internal vendor key (e.g. "zhsunyco") when a device model has no
|
|
271
|
+
// explicit `manufacturer` of its own - see `DeviceMetadata.manufacturer`'s doc comment.
|
|
272
|
+
manufacturer: metadata.manufacturer ?? target.vendor,
|
|
273
|
+
label: metadata.label,
|
|
274
|
+
width,
|
|
275
|
+
height,
|
|
276
|
+
colours: metadata.colours,
|
|
277
|
+
description: device.description,
|
|
278
|
+
position,
|
|
279
|
+
});
|
|
263
280
|
try {
|
|
264
281
|
const apiUrl = await getApiUrl().catch((err) => {
|
|
265
282
|
app.debug(`${label}: ${err.message}`);
|
|
@@ -281,21 +298,6 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
281
298
|
bindings = (0, binding_1.findBindings)((0, fs_1.readFileSync)(templatePath, "utf-8"));
|
|
282
299
|
}
|
|
283
300
|
const rawContext = await assembleRawContext(app, apiUrl, bindings);
|
|
284
|
-
const rawPosition = (0, unwrapSignalkTree_1.unwrapSignalkTree)(app.getSelfPath("navigation.position"));
|
|
285
|
-
const position = typeof rawPosition?.latitude === "number" && typeof rawPosition?.longitude === "number"
|
|
286
|
-
? { latitude: rawPosition.latitude, longitude: rawPosition.longitude }
|
|
287
|
-
: undefined;
|
|
288
|
-
const labelContext = (0, binding_1.buildLabelContext)({
|
|
289
|
-
// Falls back to the driver's own internal vendor key (e.g. "zhsunyco") when a device model has no
|
|
290
|
-
// explicit `manufacturer` of its own - see `DeviceMetadata.manufacturer`'s doc comment.
|
|
291
|
-
manufacturer: metadata.manufacturer ?? target.vendor,
|
|
292
|
-
label: metadata.label,
|
|
293
|
-
width,
|
|
294
|
-
height,
|
|
295
|
-
colours: metadata.colours,
|
|
296
|
-
description: device.description,
|
|
297
|
-
position,
|
|
298
|
-
});
|
|
299
301
|
// Hashed before `meta` is merged in below, deliberately, so a template merely *displaying* the
|
|
300
302
|
// repaint timestamp doesn't perpetually invalidate its own dedup and force a repaint every check. A
|
|
301
303
|
// full paint flashes the whole panel several times, and there's no confirmed partial-refresh path
|
|
@@ -320,6 +322,7 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
320
322
|
repainted: new Date().toISOString(),
|
|
321
323
|
local_zone: (0, formatters_1.resolveLocalZoneAbbreviation)(rawContext),
|
|
322
324
|
plugin_version: pluginVersion_1.PLUGIN_VERSION,
|
|
325
|
+
// Undocumented legacy alias of `label.description` - kept so templates already using it keep working.
|
|
323
326
|
description: device.description ?? "",
|
|
324
327
|
},
|
|
325
328
|
};
|
|
@@ -344,7 +347,10 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
344
347
|
height: metadata.height,
|
|
345
348
|
colours: metadata.colours,
|
|
346
349
|
});
|
|
347
|
-
bitmap = await renderer.render(fallbackPath, {
|
|
350
|
+
bitmap = await renderer.render(fallbackPath, {
|
|
351
|
+
label: labelContext,
|
|
352
|
+
meta: { repainted: new Date().toISOString(), plugin_version: pluginVersion_1.PLUGIN_VERSION, description: device.description ?? "" },
|
|
353
|
+
}, width, height, templatesDir, config_1.BUNDLED_TEMPLATES_DIR);
|
|
348
354
|
}
|
|
349
355
|
const connectTimeoutMs = (device.advanced?.paintConnectTimeoutSeconds ?? config.paintConnectTimeoutSeconds) * 1000;
|
|
350
356
|
const gattBackend = config.useBleApi && app.bleApi ? (0, bleBackend_1.bleApiBackend)(app.bleApi, pluginVersion_1.PLUGIN_NAME) : undefined;
|
|
@@ -357,7 +363,7 @@ async function considerRepaint(app, config, device, target, state, getApiUrl) {
|
|
|
357
363
|
pid: target.pid,
|
|
358
364
|
aesKey: device.advanced?.aesKey,
|
|
359
365
|
connectTimeoutMs,
|
|
360
|
-
reframe: device.reframe,
|
|
366
|
+
reframe: device.advanced?.reframe,
|
|
361
367
|
mirror: device.advanced?.mirror,
|
|
362
368
|
compress: device.advanced?.compress,
|
|
363
369
|
compressionFormat: device.advanced?.compressionFormat,
|
package/dist/resolveApiUrl.js
CHANGED
|
@@ -37,6 +37,9 @@ async function probe(url) {
|
|
|
37
37
|
* and uses the first that responds.
|
|
38
38
|
*/
|
|
39
39
|
async function resolveSignalkApiUrl(configuredUrl) {
|
|
40
|
+
// Typed free-form in the config UI, so tolerate stray whitespace and a trailing slash, which would
|
|
41
|
+
// otherwise double up with the leading slash of every API path appended to it.
|
|
42
|
+
configuredUrl = configuredUrl?.trim().replace(/\/+$/, "") || undefined;
|
|
40
43
|
const candidates = configuredUrl ? [configuredUrl] : exports.SIGNALK_API_URL_OPTIONS;
|
|
41
44
|
for (const url of candidates) {
|
|
42
45
|
if (await probe(url))
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizomatics/signalk-einklabel-plugin",
|
|
3
|
-
"version": "1.3.0-
|
|
3
|
+
"version": "1.3.0-beta12",
|
|
4
4
|
"description": "Display SignalK data on eInk Electronic Shelf Labels, includes working examples for tide clock and watch schedule.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ble",
|
|
7
7
|
"display",
|
|
8
|
+
"e-ink",
|
|
9
|
+
"e-paper",
|
|
8
10
|
"eink",
|
|
11
|
+
"epaper",
|
|
9
12
|
"esl",
|
|
10
13
|
"gicisky",
|
|
11
14
|
"instrument",
|
|
15
|
+
"marine",
|
|
16
|
+
"shelf-label",
|
|
12
17
|
"signalk",
|
|
13
18
|
"signalk-category-hardware",
|
|
14
19
|
"signalk-category-instruments",
|
|
15
20
|
"signalk-node-server-plugin",
|
|
16
21
|
"tides",
|
|
17
22
|
"watch-schedule",
|
|
23
|
+
"wolink",
|
|
18
24
|
"zhsunyco"
|
|
19
25
|
],
|
|
20
26
|
"homepage": "https://signalk-einklabel.rhizomatics.org.uk/",
|
|
@@ -56,7 +62,7 @@
|
|
|
56
62
|
"@fontsource/roboto-serif": "^5.3.0",
|
|
57
63
|
"@naugehyde/node-ble": "^1.13.5",
|
|
58
64
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
59
|
-
"@xmldom/xmldom": "^0.9.
|
|
65
|
+
"@xmldom/xmldom": "^0.9.12",
|
|
60
66
|
"commander": "^15.0.0",
|
|
61
67
|
"form-data": "^4.0.6",
|
|
62
68
|
"luxon": "^3.7.2",
|
|
@@ -65,11 +71,11 @@
|
|
|
65
71
|
},
|
|
66
72
|
"devDependencies": {
|
|
67
73
|
"@signalk/server-api": "^2.32.0",
|
|
68
|
-
"@types/luxon": "^3.7.
|
|
69
|
-
"@types/node": "^26.
|
|
74
|
+
"@types/luxon": "^3.7.5",
|
|
75
|
+
"@types/node": "^26.6.0",
|
|
70
76
|
"@types/pngjs": "^6.0.5",
|
|
71
|
-
"oxfmt": "^0.
|
|
72
|
-
"oxlint": "^1.
|
|
77
|
+
"oxfmt": "^0.70.0",
|
|
78
|
+
"oxlint": "^1.85.0",
|
|
73
79
|
"oxlint-tsgolint": "^7.0.2001",
|
|
74
80
|
"ts-node": "^10.9.2",
|
|
75
81
|
"typescript": "^5.5.0"
|
|
@@ -87,9 +93,9 @@
|
|
|
87
93
|
"docs/assets/screenshots/label_config.png"
|
|
88
94
|
],
|
|
89
95
|
"recommends": [
|
|
96
|
+
"@rhizomatics/signalk-einklabel-genai-plugin",
|
|
90
97
|
"signalk-watch-schedule",
|
|
91
|
-
"signalk-tides"
|
|
92
|
-
"@rhizomatics/signalk-einklabel-genai-plugin"
|
|
98
|
+
"signalk-tides"
|
|
93
99
|
]
|
|
94
100
|
}
|
|
95
101
|
}
|