@rhizomatics/signalk-einklabel-plugin 0.10.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +111 -9
- package/dist/cli/index.d.ts +15 -1
- package/dist/cli/index.js +103 -37
- package/dist/config.d.ts +27 -7
- package/dist/config.js +40 -10
- package/dist/devices/gicisky/compression.d.ts +16 -0
- package/dist/devices/gicisky/compression.js +34 -0
- package/dist/devices/gicisky/encode.d.ts +10 -0
- package/dist/devices/gicisky/encode.js +130 -0
- package/dist/devices/gicisky/index.d.ts +11 -0
- package/dist/devices/gicisky/index.js +164 -0
- package/dist/devices/gicisky/layout.d.ts +35 -0
- package/dist/devices/gicisky/layout.js +35 -0
- package/dist/devices/gicisky/metadata.d.ts +16 -0
- package/dist/devices/gicisky/metadata.js +127 -0
- package/dist/devices/gicisky/protocol.d.ts +53 -0
- package/dist/devices/gicisky/protocol.js +88 -0
- package/dist/devices/types.d.ts +11 -0
- package/dist/devices/zhsunyco/index.js +3 -1
- package/dist/devices/zhsunyco/metadata.js +9 -0
- package/dist/index.d.ts +36 -3
- package/dist/index.js +28 -3
- package/dist/plugin.js +2 -0
- package/dist/render/binding.d.ts +86 -3
- package/dist/render/binding.js +135 -3
- package/dist/render/formatters.js +18 -0
- package/dist/render/reframe.d.ts +12 -0
- package/dist/render/reframe.js +36 -0
- package/dist/render/templateProviders.d.ts +58 -0
- package/dist/render/templateProviders.js +16 -0
- package/dist/repaintScheduler.js +126 -51
- package/package.json +15 -12
- package/templates/.error/250x128-BWRY.svg +12 -0
- package/templates/.error/416x240-BWRY.svg +12 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GICISKY_PID_METADATA = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Keyed by the 14-bit `deviceId` decoded from the advertisement (see `decodeAdvertisedInfo` in
|
|
6
|
+
* `protocol.ts`) - Gicisky's own namespace, not shared with other vendors' PID spaces.
|
|
7
|
+
*
|
|
8
|
+
* Sourced from `DEVICE_TYPES` in hass-gicisky's `gicisky_ble/devices.py`, the more actively
|
|
9
|
+
* maintained of the two reference implementations in the sibling `lab` checkouts. That file also
|
|
10
|
+
* carries a much longer comment table of `deviceId`s seen in the wild that aren't yet mapped to
|
|
11
|
+
* physical panel facts (`width`/`height`/colours) - those aren't reproduced here since there'd be
|
|
12
|
+
* nothing to paint correctly for them yet.
|
|
13
|
+
*
|
|
14
|
+
* `width`/`height` are the pre-rotation "visual" canvas size - what a template should be designed
|
|
15
|
+
* at - matching `DeviceEntry.width`/`.height` there; `paintLayout` (`layout.ts`) then rotates/mirrors
|
|
16
|
+
* during encoding to produce the physical wire layout.
|
|
17
|
+
*/
|
|
18
|
+
exports.GICISKY_PID_METADATA = [
|
|
19
|
+
{
|
|
20
|
+
pid: 0x00a0,
|
|
21
|
+
manufacturer: "Gicisky",
|
|
22
|
+
label: '2.1" TFT BW',
|
|
23
|
+
width: 250,
|
|
24
|
+
height: 132,
|
|
25
|
+
voffset: 0,
|
|
26
|
+
colours: bw(),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
pid: 0x000b,
|
|
30
|
+
manufacturer: "Gicisky",
|
|
31
|
+
label: '2.1" BWR',
|
|
32
|
+
width: 212,
|
|
33
|
+
height: 104,
|
|
34
|
+
voffset: 0,
|
|
35
|
+
colours: bwr(),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pid: 0x010b,
|
|
39
|
+
manufacturer: "Gicisky",
|
|
40
|
+
label: '2.1" BWR',
|
|
41
|
+
width: 250,
|
|
42
|
+
height: 128,
|
|
43
|
+
voffset: 0,
|
|
44
|
+
colours: bwr(),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pid: 0x0028,
|
|
48
|
+
manufacturer: "Gicisky",
|
|
49
|
+
label: '2.9" BW',
|
|
50
|
+
width: 296,
|
|
51
|
+
height: 128,
|
|
52
|
+
voffset: 0,
|
|
53
|
+
colours: bw(),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pid: 0x0033,
|
|
57
|
+
manufacturer: "Gicisky",
|
|
58
|
+
label: '2.9" BWR',
|
|
59
|
+
width: 296,
|
|
60
|
+
height: 128,
|
|
61
|
+
voffset: 0,
|
|
62
|
+
colours: bwr(),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pid: 0x002e,
|
|
66
|
+
manufacturer: "Gicisky",
|
|
67
|
+
label: '2.9" BWRY',
|
|
68
|
+
width: 296,
|
|
69
|
+
height: 128,
|
|
70
|
+
voffset: 0,
|
|
71
|
+
colours: bwry(),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pid: 0x022b,
|
|
75
|
+
manufacturer: "Gicisky",
|
|
76
|
+
label: '3.7" BWR',
|
|
77
|
+
width: 240,
|
|
78
|
+
height: 416,
|
|
79
|
+
voffset: 0,
|
|
80
|
+
colours: bwr(),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
pid: 0x004b,
|
|
84
|
+
manufacturer: "Gicisky",
|
|
85
|
+
label: '4.2" BWR',
|
|
86
|
+
width: 400,
|
|
87
|
+
height: 300,
|
|
88
|
+
voffset: 0,
|
|
89
|
+
colours: bwr(),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pid: 0x004e,
|
|
93
|
+
manufacturer: "Gicisky",
|
|
94
|
+
label: '4.2" BWRY',
|
|
95
|
+
width: 400,
|
|
96
|
+
height: 300,
|
|
97
|
+
voffset: 0,
|
|
98
|
+
colours: bwry(),
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
pid: 0x012b,
|
|
102
|
+
manufacturer: "Gicisky",
|
|
103
|
+
label: '7.5" BWR',
|
|
104
|
+
width: 800,
|
|
105
|
+
height: 480,
|
|
106
|
+
voffset: 0,
|
|
107
|
+
colours: bwr(),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
pid: 0x008b,
|
|
111
|
+
manufacturer: "Gicisky",
|
|
112
|
+
label: '10.2" BWR',
|
|
113
|
+
width: 960,
|
|
114
|
+
height: 640,
|
|
115
|
+
voffset: 0,
|
|
116
|
+
colours: bwr(),
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
function bw() {
|
|
120
|
+
return ["black", "white"];
|
|
121
|
+
}
|
|
122
|
+
function bwr() {
|
|
123
|
+
return ["black", "white", "red"];
|
|
124
|
+
}
|
|
125
|
+
function bwry() {
|
|
126
|
+
return ["black", "white", "red", "yellow"];
|
|
127
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gicisky ESL BLE protocol, transcribed from two reference implementations in the sibling
|
|
3
|
+
* `lab` checkouts:
|
|
4
|
+
* - gicisky-tag (github.com/xxxxx/gicisky-tag) - a minimal CLI writer/scanner.
|
|
5
|
+
* - hass-gicisky (github.com/eigger/hass-gicisky) - the more actively maintained Home
|
|
6
|
+
* Assistant integration, whose `gicisky_ble` package this mostly follows.
|
|
7
|
+
*/
|
|
8
|
+
/** BLE manufacturer ID Gicisky devices advertise under (20563 decimal). */
|
|
9
|
+
export declare const GICISKY_MANUFACTURER_ID = 20563;
|
|
10
|
+
/**
|
|
11
|
+
* The two GATT characteristics used for painting, found dynamically rather than hardcoded:
|
|
12
|
+
* both reference drivers walk every service whose UUID starts `0000f...`, collect their
|
|
13
|
+
* characteristics, and sort by the 16-bit UUID value - the lowest (observed as `fef1`) is the
|
|
14
|
+
* command/status characteristic, the next (observed as `fef2`) carries image data. Doing the
|
|
15
|
+
* same walk here (see `findCommandAndImageCharacteristics` in `index.ts`) survives a firmware
|
|
16
|
+
* that puts these under a different parent service UUID than expected.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CANDIDATE_SERVICE_UUID_PREFIX = "0000f";
|
|
19
|
+
export interface AdvertisedInfo {
|
|
20
|
+
/** `((data[4] << 8) | data[0]) & 0x3FFF` - matches this driver's `GICISKY_PID_METADATA` keys. */
|
|
21
|
+
deviceId: number;
|
|
22
|
+
/** Un-masked `(data[4] << 8) | data[0])`, kept only for logging - not currently used to disambiguate models. */
|
|
23
|
+
hardware: number;
|
|
24
|
+
firmware: number;
|
|
25
|
+
batteryMv: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decodes the 5-byte manufacturer-data payload Gicisky devices advertise. Mirrors
|
|
29
|
+
* `GiciskyBluetoothDeviceData._parse_gicisky` (hass-gicisky's `parser.py`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodeAdvertisedInfo(data: Buffer): AdvertisedInfo | undefined;
|
|
32
|
+
export declare const COMMAND: {
|
|
33
|
+
readonly requestBlockSize: 1;
|
|
34
|
+
readonly writeScreen: 2;
|
|
35
|
+
readonly startImageTransfer: 3;
|
|
36
|
+
};
|
|
37
|
+
/** `write_start_with_response`'s ack: `[0x01, <blockSize LE16>]` - observed as 244 on real hardware. */
|
|
38
|
+
export declare function decodeBlockSize(ack: Buffer): number | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Builds the `writeScreen` command announcing the total encoded payload size.
|
|
41
|
+
*
|
|
42
|
+
* `compression2` devices (7.5"/10.2") expect a 6-byte packet with a trailing `0x01` flag byte;
|
|
43
|
+
* every other device expects an 8-byte packet with 3 trailing zero bytes. Mirrors
|
|
44
|
+
* `GiciskyClient._make_cmd_packet` (hass-gicisky's `writer.py`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function writeScreenCommand(payloadLength: number, compression2: boolean): Buffer;
|
|
47
|
+
/** Builds one image-data packet: a 4-byte LE part index followed by that part's chunk. */
|
|
48
|
+
export declare function imageChunkPacket(part: number, chunk: Buffer): Buffer;
|
|
49
|
+
/** Ack shared by `startImageTransfer` and every image-data chunk: `[0x05, 0x00, <nextPart LE32>]`. */
|
|
50
|
+
export declare function decodeTransferAck(ack: Buffer): {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
nextPart: number;
|
|
53
|
+
} | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gicisky ESL BLE protocol, transcribed from two reference implementations in the sibling
|
|
4
|
+
* `lab` checkouts:
|
|
5
|
+
* - gicisky-tag (github.com/xxxxx/gicisky-tag) - a minimal CLI writer/scanner.
|
|
6
|
+
* - hass-gicisky (github.com/eigger/hass-gicisky) - the more actively maintained Home
|
|
7
|
+
* Assistant integration, whose `gicisky_ble` package this mostly follows.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.COMMAND = exports.CANDIDATE_SERVICE_UUID_PREFIX = exports.GICISKY_MANUFACTURER_ID = void 0;
|
|
11
|
+
exports.decodeAdvertisedInfo = decodeAdvertisedInfo;
|
|
12
|
+
exports.decodeBlockSize = decodeBlockSize;
|
|
13
|
+
exports.writeScreenCommand = writeScreenCommand;
|
|
14
|
+
exports.imageChunkPacket = imageChunkPacket;
|
|
15
|
+
exports.decodeTransferAck = decodeTransferAck;
|
|
16
|
+
/** BLE manufacturer ID Gicisky devices advertise under (20563 decimal). */
|
|
17
|
+
exports.GICISKY_MANUFACTURER_ID = 0x5053;
|
|
18
|
+
/**
|
|
19
|
+
* The two GATT characteristics used for painting, found dynamically rather than hardcoded:
|
|
20
|
+
* both reference drivers walk every service whose UUID starts `0000f...`, collect their
|
|
21
|
+
* characteristics, and sort by the 16-bit UUID value - the lowest (observed as `fef1`) is the
|
|
22
|
+
* command/status characteristic, the next (observed as `fef2`) carries image data. Doing the
|
|
23
|
+
* same walk here (see `findCommandAndImageCharacteristics` in `index.ts`) survives a firmware
|
|
24
|
+
* that puts these under a different parent service UUID than expected.
|
|
25
|
+
*/
|
|
26
|
+
exports.CANDIDATE_SERVICE_UUID_PREFIX = "0000f";
|
|
27
|
+
/**
|
|
28
|
+
* Decodes the 5-byte manufacturer-data payload Gicisky devices advertise. Mirrors
|
|
29
|
+
* `GiciskyBluetoothDeviceData._parse_gicisky` (hass-gicisky's `parser.py`).
|
|
30
|
+
*/
|
|
31
|
+
function decodeAdvertisedInfo(data) {
|
|
32
|
+
if (data.length !== 5) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const hardware = (data[4] << 8) | data[0];
|
|
36
|
+
return {
|
|
37
|
+
deviceId: hardware & 0x3fff,
|
|
38
|
+
hardware,
|
|
39
|
+
firmware: (data[2] << 8) | data[3],
|
|
40
|
+
batteryMv: data[1] * 100,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
exports.COMMAND = {
|
|
44
|
+
requestBlockSize: 0x01,
|
|
45
|
+
writeScreen: 0x02,
|
|
46
|
+
startImageTransfer: 0x03,
|
|
47
|
+
};
|
|
48
|
+
/** `write_start_with_response`'s ack: `[0x01, <blockSize LE16>]` - observed as 244 on real hardware. */
|
|
49
|
+
function decodeBlockSize(ack) {
|
|
50
|
+
if (ack.length < 3 || ack[0] !== exports.COMMAND.requestBlockSize) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return ack.readUInt16LE(1);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds the `writeScreen` command announcing the total encoded payload size.
|
|
57
|
+
*
|
|
58
|
+
* `compression2` devices (7.5"/10.2") expect a 6-byte packet with a trailing `0x01` flag byte;
|
|
59
|
+
* every other device expects an 8-byte packet with 3 trailing zero bytes. Mirrors
|
|
60
|
+
* `GiciskyClient._make_cmd_packet` (hass-gicisky's `writer.py`).
|
|
61
|
+
*/
|
|
62
|
+
function writeScreenCommand(payloadLength, compression2) {
|
|
63
|
+
if (compression2) {
|
|
64
|
+
const packet = Buffer.alloc(6);
|
|
65
|
+
packet[0] = exports.COMMAND.writeScreen;
|
|
66
|
+
packet.writeUInt32LE(payloadLength, 1);
|
|
67
|
+
packet[5] = 0x01;
|
|
68
|
+
return packet;
|
|
69
|
+
}
|
|
70
|
+
const packet = Buffer.alloc(8);
|
|
71
|
+
packet[0] = exports.COMMAND.writeScreen;
|
|
72
|
+
packet.writeUInt32LE(payloadLength, 1);
|
|
73
|
+
return packet;
|
|
74
|
+
}
|
|
75
|
+
/** Builds one image-data packet: a 4-byte LE part index followed by that part's chunk. */
|
|
76
|
+
function imageChunkPacket(part, chunk) {
|
|
77
|
+
const packet = Buffer.alloc(4 + chunk.length);
|
|
78
|
+
packet.writeUInt32LE(part, 0);
|
|
79
|
+
chunk.copy(packet, 4);
|
|
80
|
+
return packet;
|
|
81
|
+
}
|
|
82
|
+
/** Ack shared by `startImageTransfer` and every image-data chunk: `[0x05, 0x00, <nextPart LE32>]`. */
|
|
83
|
+
function decodeTransferAck(ack) {
|
|
84
|
+
if (ack.length < 6 || ack[0] !== 0x05) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return { ok: ack[1] === 0x00, nextPart: ack.readUInt32LE(2) };
|
|
88
|
+
}
|
package/dist/devices/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Device } from "@naugehyde/node-ble";
|
|
2
2
|
import { Bitmap } from "../render/types";
|
|
3
|
+
import { ReframeMode } from "../render/reframe";
|
|
3
4
|
export type Colour = "black" | "white" | "red" | "yellow";
|
|
4
5
|
/**
|
|
5
6
|
* Static facts about one device model, keyed by (vendor, pid) by the registry —
|
|
@@ -13,6 +14,14 @@ export interface DeviceMetadata {
|
|
|
13
14
|
* that should be treated as the default/fallback for that PID.
|
|
14
15
|
*/
|
|
15
16
|
hwVersion?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Real-world brand name (e.g. "Zhsunyco") - distinct from `VendorDriver.vendor` (e.g. also
|
|
19
|
+
* "zhsunyco"), which is this codebase's internal BLE-protocol driver key rather than a name meant
|
|
20
|
+
* for display. Optional since a CLI/config `DeviceModelOverride` for unsupported hardware has no
|
|
21
|
+
* table entry to source it from - `considerLlmPromptRepaint` (repaintScheduler.ts) falls back to
|
|
22
|
+
* the driver's own `vendor` key when unset, for a `source=label,path=manufacturer` prompt binding.
|
|
23
|
+
*/
|
|
24
|
+
manufacturer?: string;
|
|
16
25
|
label: string;
|
|
17
26
|
width: number;
|
|
18
27
|
height: number;
|
|
@@ -49,6 +58,8 @@ export interface VendorDeviceConfig {
|
|
|
49
58
|
modelOverride?: DeviceModelOverride;
|
|
50
59
|
/** How long to wait for the BLE connect step before giving up - if omitted, the driver picks its own default. */
|
|
51
60
|
connectTimeoutMs?: number;
|
|
61
|
+
/** How to fit the bitmap onto the panel when its size doesn't already match - see `ReframeMode`. Defaults to `"fixed"`, i.e. unchanged (encoding then rejects the mismatch, as it always has). */
|
|
62
|
+
reframe?: ReframeMode;
|
|
52
63
|
}
|
|
53
64
|
export interface VendorDriver {
|
|
54
65
|
vendor: string;
|
|
@@ -5,6 +5,7 @@ const bleDiscovery_1 = require("../bleDiscovery");
|
|
|
5
5
|
const pluginVersion_1 = require("../../pluginVersion");
|
|
6
6
|
const metadata_1 = require("./metadata");
|
|
7
7
|
const encode_1 = require("./encode");
|
|
8
|
+
const reframe_1 = require("../../render/reframe");
|
|
8
9
|
const protocol_1 = require("./protocol");
|
|
9
10
|
/** node-ble has no MTU API; this matches the reference driver's mtu(247)-9 default. */
|
|
10
11
|
const UPLOAD_CHUNK_SIZE = 238;
|
|
@@ -86,7 +87,8 @@ class ZhsunycoDriver {
|
|
|
86
87
|
const challenge = await authChar.readValue();
|
|
87
88
|
await authChar.writeValueWithoutResponse((0, protocol_1.authResponse)(challenge, aesKey));
|
|
88
89
|
await (0, bleDiscovery_1.sleep)(AUTH_SETTLE_DELAY_MS);
|
|
89
|
-
const
|
|
90
|
+
const framed = (0, reframe_1.reframeBitmap)(bitmap, metadata.width, metadata.height - metadata.voffset, config.reframe ?? "fixed");
|
|
91
|
+
const pixelData = (0, encode_1.encodeBitmap)(framed, metadata);
|
|
90
92
|
for (let offset = 0; offset < pixelData.length; offset += UPLOAD_CHUNK_SIZE) {
|
|
91
93
|
const chunk = pixelData.subarray(offset, offset + UPLOAD_CHUNK_SIZE);
|
|
92
94
|
await dataChar.writeValueWithResponse(Buffer.concat([(0, protocol_1.commandHeader)(protocol_1.COMMAND.uploadBlock, offset), chunk]));
|
|
@@ -22,6 +22,7 @@ exports.ZHSUNYCO_PID_METADATA = void 0;
|
|
|
22
22
|
exports.ZHSUNYCO_PID_METADATA = [
|
|
23
23
|
{
|
|
24
24
|
pid: 0x0008,
|
|
25
|
+
manufacturer: "Zhsunyco",
|
|
25
26
|
label: '1.54"',
|
|
26
27
|
width: 200,
|
|
27
28
|
height: 200,
|
|
@@ -30,6 +31,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
30
31
|
},
|
|
31
32
|
{
|
|
32
33
|
pid: 0x000a,
|
|
34
|
+
manufacturer: "Zhsunyco",
|
|
33
35
|
label: '2.13"',
|
|
34
36
|
width: 250,
|
|
35
37
|
height: 128,
|
|
@@ -38,6 +40,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
38
40
|
},
|
|
39
41
|
{
|
|
40
42
|
pid: 0x000e,
|
|
43
|
+
manufacturer: "Zhsunyco",
|
|
41
44
|
label: '3.7"',
|
|
42
45
|
width: 416,
|
|
43
46
|
height: 240,
|
|
@@ -47,6 +50,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
47
50
|
{
|
|
48
51
|
pid: 0x000e,
|
|
49
52
|
hwVersion: "0103",
|
|
53
|
+
manufacturer: "Zhsunyco",
|
|
50
54
|
label: '2.13"',
|
|
51
55
|
width: 250,
|
|
52
56
|
height: 128,
|
|
@@ -56,6 +60,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
56
60
|
{
|
|
57
61
|
pid: 0x000e,
|
|
58
62
|
hwVersion: "0201",
|
|
63
|
+
manufacturer: "Zhsunyco",
|
|
59
64
|
label: '3.5"',
|
|
60
65
|
width: 384,
|
|
61
66
|
height: 184,
|
|
@@ -65,6 +70,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
65
70
|
{
|
|
66
71
|
pid: 0x000e,
|
|
67
72
|
hwVersion: "0203",
|
|
73
|
+
manufacturer: "Zhsunyco",
|
|
68
74
|
label: '7.5"',
|
|
69
75
|
width: 800,
|
|
70
76
|
height: 480,
|
|
@@ -73,6 +79,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
73
79
|
},
|
|
74
80
|
{
|
|
75
81
|
pid: 0x0012,
|
|
82
|
+
manufacturer: "Zhsunyco",
|
|
76
83
|
label: '2.9"',
|
|
77
84
|
width: 296,
|
|
78
85
|
height: 128,
|
|
@@ -81,6 +88,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
81
88
|
},
|
|
82
89
|
{
|
|
83
90
|
pid: 0x0016,
|
|
91
|
+
manufacturer: "Zhsunyco",
|
|
84
92
|
label: '4.2"',
|
|
85
93
|
width: 400,
|
|
86
94
|
height: 300,
|
|
@@ -89,6 +97,7 @@ exports.ZHSUNYCO_PID_METADATA = [
|
|
|
89
97
|
},
|
|
90
98
|
{
|
|
91
99
|
pid: 0x001a,
|
|
100
|
+
manufacturer: "Zhsunyco",
|
|
92
101
|
label: '5.8"',
|
|
93
102
|
width: 648,
|
|
94
103
|
height: 480,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { ServerAPI, Plugin } from "@signalk/server-api";
|
|
2
2
|
import { registerDriver, getDriver, allDrivers } from "./devices/registry";
|
|
3
|
+
import { registerTemplateProvider as registerTemplateProviderImpl, findTemplateProvider as findTemplateProviderImpl, allTemplateProviders as allTemplateProvidersImpl } from "./render/templateProviders";
|
|
4
|
+
import { SvgRenderer } from "./render/svgRenderer";
|
|
5
|
+
import { bitmapToPng as bitmapToPngImpl } from "./render/png";
|
|
6
|
+
import { buildLabelContext, findTextBindings, substituteTextBindings } from "./render/binding";
|
|
3
7
|
import type { VendorDriver as VendorDriverType, DeviceMetadata as DeviceMetadataType, DiscoveredDevice as DiscoveredDeviceType, VendorDeviceConfig as VendorDeviceConfigType, Colour as ColourType } from "./devices/types";
|
|
8
|
+
import type { TemplateProvider as TemplateProviderType, TemplateRenderRequest as TemplateRenderRequestType } from "./render/templateProviders";
|
|
9
|
+
import type { LabelMeta as LabelMetaType } from "./render/binding";
|
|
10
|
+
import type { Bitmap as BitmapType, TemplateContext as TemplateContextType } from "./render/types";
|
|
4
11
|
/**
|
|
5
12
|
* Public extension point for vendor packages. A package that adds support for a new
|
|
6
13
|
* ESL vendor (e.g. `signalk-esl-shoplabelcorp-plugin`) imports this module and calls
|
|
@@ -8,19 +15,45 @@ import type { VendorDriver as VendorDriverType, DeviceMetadata as DeviceMetadata
|
|
|
8
15
|
* `start()` (or at module load time). There's no scanning of installed packages -
|
|
9
16
|
* registration is always an explicit call by the extension's own code.
|
|
10
17
|
*
|
|
11
|
-
* Declare this package as a `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* Declare this package as a regular `dependency` in the extension package (**not** a
|
|
19
|
+
* `peerDependency` - the SignalK team's own guidance is that npm's peer-dependency resolution
|
|
20
|
+
* interacts poorly with the server's plugin install layout), and declare the SignalK-level
|
|
21
|
+
* relationship via `"signalk": { "requires": ["@rhizomatics/signalk-einklabel-plugin"] }` in the
|
|
22
|
+
* extension's own package.json instead, so the App Store can install/report the dependency.
|
|
14
23
|
*/
|
|
15
24
|
declare function plugin(app: ServerAPI): Plugin;
|
|
16
25
|
declare namespace plugin {
|
|
17
26
|
const registerVendorDriver: typeof registerDriver;
|
|
18
27
|
const getVendorDriver: typeof getDriver;
|
|
19
28
|
const allVendorDrivers: typeof allDrivers;
|
|
29
|
+
/**
|
|
30
|
+
* Public extension point for a package offering an alternative to hand-authored SVG templates - e.g.
|
|
31
|
+
* `@rhizomatics/signalk-einklabel-genai-plugin` generating content from an LLM prompt - see
|
|
32
|
+
* `TemplateProvider`'s own doc comment (`./render/templateProviders.ts`) for the full contract. Same
|
|
33
|
+
* regular-`dependency`-plus-`signalk.requires` convention as `registerVendorDriver` above.
|
|
34
|
+
*/
|
|
35
|
+
const registerTemplateProvider: typeof registerTemplateProviderImpl;
|
|
36
|
+
const getTemplateProvider: typeof findTemplateProviderImpl;
|
|
37
|
+
const allTemplateProviders: typeof allTemplateProvidersImpl;
|
|
38
|
+
/** Rasterizes an SVG string to a `Bitmap` - a template provider needs this to turn whatever SVG it produces (e.g. an LLM's response) into paintable pixels, exactly as a bundled template is rendered. */
|
|
39
|
+
const Renderer: typeof SvgRenderer;
|
|
40
|
+
/** Encodes a `Bitmap` as PNG bytes - useful for a CLI extension writing a preview file, same as the core plugin's own `render`/`generate` commands. */
|
|
41
|
+
const bitmapToPng: typeof bitmapToPngImpl;
|
|
42
|
+
/** Facts about one physical label (`source=label,path=...` bindings resolve against these) - see `./render/binding.ts`. */
|
|
43
|
+
const buildLabel: typeof buildLabelContext;
|
|
44
|
+
/** Every `{...}` placeholder referenced across one or more text fragments, parsed as bindings - see `./render/binding.ts`. */
|
|
45
|
+
const findBindingsInText: typeof findTextBindings;
|
|
46
|
+
/** Substitutes every `{...}` placeholder in `text` with its resolved binding value - see `./render/binding.ts`. */
|
|
47
|
+
const substituteBindingsInText: typeof substituteTextBindings;
|
|
20
48
|
type VendorDriver = VendorDriverType;
|
|
21
49
|
type DeviceMetadata = DeviceMetadataType;
|
|
22
50
|
type DiscoveredDevice = DiscoveredDeviceType;
|
|
23
51
|
type VendorDeviceConfig = VendorDeviceConfigType;
|
|
24
52
|
type Colour = ColourType;
|
|
53
|
+
type TemplateProvider = TemplateProviderType;
|
|
54
|
+
type TemplateRenderRequest = TemplateRenderRequestType;
|
|
55
|
+
type LabelMeta = LabelMetaType;
|
|
56
|
+
type Bitmap = BitmapType;
|
|
57
|
+
type TemplateContext = TemplateContextType;
|
|
25
58
|
}
|
|
26
59
|
export = plugin;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const plugin_1 = require("./plugin");
|
|
3
3
|
const registry_1 = require("./devices/registry");
|
|
4
|
+
const templateProviders_1 = require("./render/templateProviders");
|
|
5
|
+
const svgRenderer_1 = require("./render/svgRenderer");
|
|
6
|
+
const png_1 = require("./render/png");
|
|
7
|
+
const binding_1 = require("./render/binding");
|
|
4
8
|
/**
|
|
5
9
|
* Public extension point for vendor packages. A package that adds support for a new
|
|
6
10
|
* ESL vendor (e.g. `signalk-esl-shoplabelcorp-plugin`) imports this module and calls
|
|
@@ -8,9 +12,11 @@ const registry_1 = require("./devices/registry");
|
|
|
8
12
|
* `start()` (or at module load time). There's no scanning of installed packages -
|
|
9
13
|
* registration is always an explicit call by the extension's own code.
|
|
10
14
|
*
|
|
11
|
-
* Declare this package as a `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
15
|
+
* Declare this package as a regular `dependency` in the extension package (**not** a
|
|
16
|
+
* `peerDependency` - the SignalK team's own guidance is that npm's peer-dependency resolution
|
|
17
|
+
* interacts poorly with the server's plugin install layout), and declare the SignalK-level
|
|
18
|
+
* relationship via `"signalk": { "requires": ["@rhizomatics/signalk-einklabel-plugin"] }` in the
|
|
19
|
+
* extension's own package.json instead, so the App Store can install/report the dependency.
|
|
14
20
|
*/
|
|
15
21
|
function plugin(app) {
|
|
16
22
|
return (0, plugin_1.createPlugin)(app);
|
|
@@ -19,5 +25,24 @@ function plugin(app) {
|
|
|
19
25
|
plugin.registerVendorDriver = registry_1.registerDriver;
|
|
20
26
|
plugin.getVendorDriver = registry_1.getDriver;
|
|
21
27
|
plugin.allVendorDrivers = registry_1.allDrivers;
|
|
28
|
+
/**
|
|
29
|
+
* Public extension point for a package offering an alternative to hand-authored SVG templates - e.g.
|
|
30
|
+
* `@rhizomatics/signalk-einklabel-genai-plugin` generating content from an LLM prompt - see
|
|
31
|
+
* `TemplateProvider`'s own doc comment (`./render/templateProviders.ts`) for the full contract. Same
|
|
32
|
+
* regular-`dependency`-plus-`signalk.requires` convention as `registerVendorDriver` above.
|
|
33
|
+
*/
|
|
34
|
+
plugin.registerTemplateProvider = templateProviders_1.registerTemplateProvider;
|
|
35
|
+
plugin.getTemplateProvider = templateProviders_1.findTemplateProvider;
|
|
36
|
+
plugin.allTemplateProviders = templateProviders_1.allTemplateProviders;
|
|
37
|
+
/** Rasterizes an SVG string to a `Bitmap` - a template provider needs this to turn whatever SVG it produces (e.g. an LLM's response) into paintable pixels, exactly as a bundled template is rendered. */
|
|
38
|
+
plugin.Renderer = svgRenderer_1.SvgRenderer;
|
|
39
|
+
/** Encodes a `Bitmap` as PNG bytes - useful for a CLI extension writing a preview file, same as the core plugin's own `render`/`generate` commands. */
|
|
40
|
+
plugin.bitmapToPng = png_1.bitmapToPng;
|
|
41
|
+
/** Facts about one physical label (`source=label,path=...` bindings resolve against these) - see `./render/binding.ts`. */
|
|
42
|
+
plugin.buildLabel = binding_1.buildLabelContext;
|
|
43
|
+
/** Every `{...}` placeholder referenced across one or more text fragments, parsed as bindings - see `./render/binding.ts`. */
|
|
44
|
+
plugin.findBindingsInText = binding_1.findTextBindings;
|
|
45
|
+
/** Substitutes every `{...}` placeholder in `text` with its resolved binding value - see `./render/binding.ts`. */
|
|
46
|
+
plugin.substituteBindingsInText = binding_1.substituteTextBindings;
|
|
22
47
|
})(plugin || (plugin = {}));
|
|
23
48
|
module.exports = plugin;
|
package/dist/plugin.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.createPlugin = createPlugin;
|
|
|
4
4
|
const config_1 = require("./config");
|
|
5
5
|
const registry_1 = require("./devices/registry");
|
|
6
6
|
const zhsunyco_1 = require("./devices/zhsunyco");
|
|
7
|
+
const gicisky_1 = require("./devices/gicisky");
|
|
7
8
|
const discoveryCoordinator_1 = require("./devices/discoveryCoordinator");
|
|
8
9
|
const discoveredDevicesStore_1 = require("./devices/discoveredDevicesStore");
|
|
9
10
|
const repaintScheduler_1 = require("./repaintScheduler");
|
|
@@ -33,6 +34,7 @@ async function runStartupScan(app, durationSeconds) {
|
|
|
33
34
|
}
|
|
34
35
|
function createPlugin(app) {
|
|
35
36
|
(0, registry_1.registerDriver)(new zhsunyco_1.ZhsunycoDriver());
|
|
37
|
+
(0, registry_1.registerDriver)(new gicisky_1.GiciskyDriver());
|
|
36
38
|
let scheduler;
|
|
37
39
|
const plugin = {
|
|
38
40
|
id: "signalk-einklabel-plugin",
|
package/dist/render/binding.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Colour } from "../devices/types";
|
|
1
2
|
import { TemplateContext } from "./types";
|
|
2
|
-
declare const SOURCES: readonly ["signalk", "resources", "einklabel"];
|
|
3
|
+
declare const SOURCES: readonly ["signalk", "resources", "einklabel", "label"];
|
|
3
4
|
type Source = (typeof SOURCES)[number];
|
|
4
5
|
/**
|
|
5
6
|
* Parsed form of a `<desc>`'s `key=value,key=value` content - see `parseBinding` for the grammar.
|
|
@@ -19,7 +20,12 @@ export interface Binding {
|
|
|
19
20
|
* for). Set this to pin a template to one provider regardless of what else is installed.
|
|
20
21
|
*/
|
|
21
22
|
provider?: string;
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* For `source === 'einklabel'`, a dotted path into the plugin's own injected `meta` (e.g. `repainted`);
|
|
25
|
+
* for `source === 'label'`, a dotted path into the physical label's own facts (`manufacturer`, `label`,
|
|
26
|
+
* `width`, `height`, `colours`, `fonts`, `description`, `position` - see `buildLabelContext` below)
|
|
27
|
+
* rather than into vessel/resource data.
|
|
28
|
+
*/
|
|
23
29
|
path: string;
|
|
24
30
|
/** A named formatter (see `./formatters.ts`), or `'raw'` to suppress automatic unit conversion (see `renderBinding`). */
|
|
25
31
|
format?: string;
|
|
@@ -35,6 +41,14 @@ export interface Binding {
|
|
|
35
41
|
* template doesn't also require duplicating its bundled asset sets.
|
|
36
42
|
*/
|
|
37
43
|
assets?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Substituted in place of the resolved value when that value is missing (`undefined`/`null`,
|
|
46
|
+
* e.g. an unpublished SignalK path) - see `renderBinding`. Distinct from an *unset* default (this
|
|
47
|
+
* field itself being `undefined`), which falls through to the pre-existing "" fallback - explicitly
|
|
48
|
+
* writing `default=` (an empty value) still counts as "given", so a binding can deliberately default
|
|
49
|
+
* to blank rather than to `substituteTextBindings`' own "???" below.
|
|
50
|
+
*/
|
|
51
|
+
default?: string;
|
|
38
52
|
}
|
|
39
53
|
/**
|
|
40
54
|
* Parses a `<desc>` element's text content into a `Binding`, e.g.
|
|
@@ -62,6 +76,15 @@ export declare function resourceContextKey(binding: Pick<Binding, "resource" | "
|
|
|
62
76
|
* way as a `<text>` binding.
|
|
63
77
|
*/
|
|
64
78
|
export declare function findBindings(svgSource: string): Binding[];
|
|
79
|
+
/**
|
|
80
|
+
* Reads the root `<svg>` element's declared `width`/`height` (falling back to the 3rd/4th `viewBox`
|
|
81
|
+
* numbers when either attribute is missing) - lets a caller default a render size to what the template
|
|
82
|
+
* itself was authored for, e.g. the CLI's -w/--width and --height options.
|
|
83
|
+
*/
|
|
84
|
+
export declare function readTemplateDimensions(svgSource: string): {
|
|
85
|
+
width?: number;
|
|
86
|
+
height?: number;
|
|
87
|
+
};
|
|
65
88
|
/** Resolves a parsed `Binding` against the render context assembled by `assembleRawContext`. */
|
|
66
89
|
export declare function resolveBinding(binding: Binding, context: TemplateContext): unknown;
|
|
67
90
|
/**
|
|
@@ -69,6 +92,8 @@ export declare function resolveBinding(binding: Binding, context: TemplateContex
|
|
|
69
92
|
* the CLI's `field`/`fields` commands show the same thing a real render would.
|
|
70
93
|
*
|
|
71
94
|
* Precedence for a numeric value:
|
|
95
|
+
* 0. A missing value (`undefined`/`null`, e.g. an unpublished path) with an explicit `default=` given -
|
|
96
|
+
* that default, verbatim, bypassing every step below (there's nothing to format).
|
|
72
97
|
* 1. An explicit named `format=` (anything other than `raw`) - `local_time`/`utc_offset`/`position`.
|
|
73
98
|
* 2. An explicit `category=` - for values with no path metadata of their own, e.g. a `source=resources`
|
|
74
99
|
* value.
|
|
@@ -76,7 +101,65 @@ export declare function resolveBinding(binding: Binding, context: TemplateContex
|
|
|
76
101
|
* `context.pathMeta`) by default - `format=raw` opts out of this step only.
|
|
77
102
|
* 4. Falls through to `round=` (`toFixed`), `JSON.stringify` for an unformatted object/array value
|
|
78
103
|
* (e.g. a path that resolved to a whole sub-tree rather than a leaf) instead of the useless
|
|
79
|
-
* `String(value)` -> `"[object Object]"`, else `String`.
|
|
104
|
+
* `String(value)` -> `"[object Object]"`, else `String`. A missing value with no `default=` given
|
|
105
|
+
* still falls through to the pre-existing "" here, unchanged from before `default=` existed.
|
|
80
106
|
*/
|
|
81
107
|
export declare function renderBinding(binding: Binding, context: TemplateContext): string;
|
|
108
|
+
/** Facts about one physical label a `source=label,path=...` binding can reference - see `buildLabelContext`. */
|
|
109
|
+
export interface LabelMeta {
|
|
110
|
+
manufacturer: string;
|
|
111
|
+
/** The physical panel's own size label, e.g. `'3.7"'` - `DeviceMetadata.label` verbatim, see `../devices/types.ts`. */
|
|
112
|
+
label: string;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
colours: Colour[];
|
|
116
|
+
description?: string;
|
|
117
|
+
position?: {
|
|
118
|
+
latitude: number;
|
|
119
|
+
longitude: number;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Builds the `context.label` object a `source=label,path=...` binding addresses (e.g.
|
|
124
|
+
* `{source=label,path=width}` in free text, or a `<desc>source=label,path=width</desc>` in an SVG
|
|
125
|
+
* template - every `{...}`/`<desc>` placeholder is a real binding, so a `label` path always needs the
|
|
126
|
+
* explicit `source=label,path=` form to disambiguate it from a `signalk` self path). `colours`/`fonts`
|
|
127
|
+
* are left as arrays (each colour entry pre-annotated with its hex code, e.g. `"black (#000000)"`)
|
|
128
|
+
* rather than joined into a single string, so a caller can either use them bare (renders as JSON) or add
|
|
129
|
+
* `format=csv` (see `./formatters.ts`) for a plain comma-separated list. `position`, if given, is rounded
|
|
130
|
+
* to ~2 decimal places (~1.1km) - fine-grained enough to be meaningfully "for here", coarse enough that
|
|
131
|
+
* ordinary GPS jitter at anchor doesn't change it tick to tick, which matters because `considerRepaint`
|
|
132
|
+
* (`../repaintScheduler.ts`) folds this whole object into every device's dedup hash - full-precision
|
|
133
|
+
* jitter here would otherwise force a repaint (and, for a provider-rendered template, a fresh paid API
|
|
134
|
+
* call) far more often than the underlying position has actually meaningfully changed.
|
|
135
|
+
*/
|
|
136
|
+
export declare function buildLabelContext(meta: LabelMeta): Record<string, unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* Every binding referenced across one or more free-text fragments - every `{...}` placeholder,
|
|
139
|
+
* deduplicated across all of `texts` combined, parsed exactly the way a template's `<desc>` binding is
|
|
140
|
+
* (`parseBinding` above), so a bare path (`{design.length}`, `source=signalk,context=self` shorthand),
|
|
141
|
+
* the full binding grammar (`{source=signalk,path=navigation.position,format=position}`), and a
|
|
142
|
+
* `source=label,path=...` binding all work uniformly. Pass the result to `assembleRawContext`
|
|
143
|
+
* (`../repaintScheduler.ts`) exactly as a template's own bindings are - it fetches the `signalk`/
|
|
144
|
+
* `resources`-sourced ones and silently ignores `label`/`einklabel`-sourced ones, which resolve directly
|
|
145
|
+
* against `context.label`/`context.meta` instead (built by the caller, not fetched).
|
|
146
|
+
*
|
|
147
|
+
* A placeholder that isn't valid binding grammar (e.g. a typo like `{source=taheight}`) is silently
|
|
148
|
+
* skipped here rather than thrown - the same per-field isolation `SvgRenderer` gives a bad `<desc>`
|
|
149
|
+
* binding, so one malformed placeholder doesn't take down the whole text. `substituteTextBindings` hits
|
|
150
|
+
* the identical parse error at substitution time and turns it into "???" for just that field.
|
|
151
|
+
*/
|
|
152
|
+
export declare function findTextBindings(...texts: string[]): Binding[];
|
|
153
|
+
/**
|
|
154
|
+
* Substitutes every `{...}` placeholder in `text` with its resolved binding value against `context`
|
|
155
|
+
* (built by `assembleRawContext` plus `context.label` from `buildLabelContext` - see
|
|
156
|
+
* `findTextBindings`). Mirrors `renderBinding`'s per-field isolation, but substitutes "???" for anything
|
|
157
|
+
* that resolves to no value at all (missing path, invalid binding grammar) rather than "" - prose with a
|
|
158
|
+
* silently-blank word reads as a fact ("for the sailor of a m vessel"), not as a gap the reader would
|
|
159
|
+
* notice. Two things override that "???": a binding that resolves successfully to a legitimately empty
|
|
160
|
+
* string (e.g. an unset `{source=label,path=description}`) is left as empty, since that's a real answer,
|
|
161
|
+
* not a miss; and a binding with an explicit `default=` uses that default instead, since the caller has
|
|
162
|
+
* already said what a missing value should read as.
|
|
163
|
+
*/
|
|
164
|
+
export declare function substituteTextBindings(text: string, context: TemplateContext): string;
|
|
82
165
|
export {};
|