signalk-garmin-keypad-plugin 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pgns.js ADDED
@@ -0,0 +1,454 @@
1
+ "use strict";
2
+ // Garmin GNX Keypad PGN definitions for canboatjs v3
3
+ //
4
+ // PGN 61184 — Button events (select/save preset, page nav)
5
+ // PGN 126720 — Property commands (display, sleep) and heartbeat
6
+ //
7
+ // Field types use LOOKUP+MANUFACTURER_CODE (not MANUFACTURER) for toPgn encoding.
8
+ module.exports = {
9
+ PGNs: [
10
+ // =========================================================
11
+ // PGN 61184 — Select Preset (Short Press)
12
+ // =========================================================
13
+ {
14
+ PGN: 61184,
15
+ Id: 'garminGnxKeypadSelectPreset',
16
+ Description: 'Garmin GNX Keypad Select Preset',
17
+ Type: 'Single',
18
+ Complete: true,
19
+ Length: 7,
20
+ Fields: [
21
+ {
22
+ Order: 1,
23
+ Id: 'manufacturerCode',
24
+ Name: 'Manufacturer Code',
25
+ BitLength: 11,
26
+ BitOffset: 0,
27
+ BitStart: 0,
28
+ Match: 229,
29
+ FieldType: 'LOOKUP',
30
+ LookupEnumeration: 'MANUFACTURER_CODE',
31
+ Signed: false
32
+ },
33
+ {
34
+ Order: 2,
35
+ Id: 'reserved',
36
+ Name: 'Reserved',
37
+ BitLength: 2,
38
+ BitOffset: 11,
39
+ BitStart: 3,
40
+ FieldType: 'RESERVED'
41
+ },
42
+ {
43
+ Order: 3,
44
+ Id: 'industryCode',
45
+ Name: 'Industry Code',
46
+ BitLength: 3,
47
+ BitOffset: 13,
48
+ BitStart: 5,
49
+ Match: 4,
50
+ FieldType: 'LOOKUP',
51
+ LookupEnumeration: 'INDUSTRY_CODE',
52
+ Signed: false
53
+ },
54
+ {
55
+ Order: 4,
56
+ Id: 'command',
57
+ Name: 'Command',
58
+ BitLength: 8,
59
+ BitOffset: 16,
60
+ BitStart: 0,
61
+ FieldType: 'NUMBER',
62
+ Resolution: 1,
63
+ Signed: false,
64
+ Match: 0x84
65
+ },
66
+ {
67
+ Order: 5,
68
+ Id: 'productId',
69
+ Name: 'Product ID',
70
+ BitLength: 8,
71
+ BitOffset: 24,
72
+ BitStart: 0,
73
+ FieldType: 'NUMBER',
74
+ Resolution: 1,
75
+ Signed: false
76
+ },
77
+ {
78
+ Order: 6,
79
+ Id: 'unknown1',
80
+ Name: 'Unknown 1',
81
+ BitLength: 8,
82
+ BitOffset: 32,
83
+ BitStart: 0,
84
+ FieldType: 'NUMBER',
85
+ Resolution: 1,
86
+ Signed: false
87
+ },
88
+ {
89
+ Order: 7,
90
+ Id: 'unknown2',
91
+ Name: 'Unknown 2',
92
+ BitLength: 8,
93
+ BitOffset: 40,
94
+ BitStart: 0,
95
+ FieldType: 'NUMBER',
96
+ Resolution: 1,
97
+ Signed: false
98
+ },
99
+ {
100
+ Order: 8,
101
+ Id: 'presetIndex',
102
+ Name: 'Preset Index',
103
+ BitLength: 8,
104
+ BitOffset: 48,
105
+ BitStart: 0,
106
+ FieldType: 'NUMBER',
107
+ Resolution: 1,
108
+ Signed: false
109
+ }
110
+ ]
111
+ },
112
+ // =========================================================
113
+ // PGN 61184 — Save Preset (Long Press)
114
+ // =========================================================
115
+ {
116
+ PGN: 61184,
117
+ Id: 'garminGnxKeypadSavePreset',
118
+ Description: 'Garmin GNX Keypad Save Preset',
119
+ Type: 'Single',
120
+ Complete: true,
121
+ Length: 7,
122
+ Fields: [
123
+ {
124
+ Order: 1,
125
+ Id: 'manufacturerCode',
126
+ Name: 'Manufacturer Code',
127
+ BitLength: 11,
128
+ BitOffset: 0,
129
+ BitStart: 0,
130
+ Match: 229,
131
+ FieldType: 'LOOKUP',
132
+ LookupEnumeration: 'MANUFACTURER_CODE',
133
+ Signed: false
134
+ },
135
+ {
136
+ Order: 2,
137
+ Id: 'reserved',
138
+ Name: 'Reserved',
139
+ BitLength: 2,
140
+ BitOffset: 11,
141
+ BitStart: 3,
142
+ FieldType: 'RESERVED'
143
+ },
144
+ {
145
+ Order: 3,
146
+ Id: 'industryCode',
147
+ Name: 'Industry Code',
148
+ BitLength: 3,
149
+ BitOffset: 13,
150
+ BitStart: 5,
151
+ Match: 4,
152
+ FieldType: 'LOOKUP',
153
+ LookupEnumeration: 'INDUSTRY_CODE',
154
+ Signed: false
155
+ },
156
+ {
157
+ Order: 4,
158
+ Id: 'command',
159
+ Name: 'Command',
160
+ BitLength: 8,
161
+ BitOffset: 16,
162
+ BitStart: 0,
163
+ FieldType: 'NUMBER',
164
+ Resolution: 1,
165
+ Signed: false,
166
+ Match: 0x85
167
+ },
168
+ {
169
+ Order: 5,
170
+ Id: 'productId',
171
+ Name: 'Product ID',
172
+ BitLength: 8,
173
+ BitOffset: 24,
174
+ BitStart: 0,
175
+ FieldType: 'NUMBER',
176
+ Resolution: 1,
177
+ Signed: false
178
+ },
179
+ {
180
+ Order: 6,
181
+ Id: 'unknown1',
182
+ Name: 'Unknown 1',
183
+ BitLength: 8,
184
+ BitOffset: 32,
185
+ BitStart: 0,
186
+ FieldType: 'NUMBER',
187
+ Resolution: 1,
188
+ Signed: false
189
+ },
190
+ {
191
+ Order: 7,
192
+ Id: 'unknown2',
193
+ Name: 'Unknown 2',
194
+ BitLength: 8,
195
+ BitOffset: 40,
196
+ BitStart: 0,
197
+ FieldType: 'NUMBER',
198
+ Resolution: 1,
199
+ Signed: false
200
+ },
201
+ {
202
+ Order: 8,
203
+ Id: 'presetIndex',
204
+ Name: 'Preset Index',
205
+ BitLength: 8,
206
+ BitOffset: 48,
207
+ BitStart: 0,
208
+ FieldType: 'NUMBER',
209
+ Resolution: 1,
210
+ Signed: false
211
+ }
212
+ ]
213
+ },
214
+ // =========================================================
215
+ // PGN 61184 — Page Navigation
216
+ // =========================================================
217
+ {
218
+ PGN: 61184,
219
+ Id: 'garminGnxKeypadPageNav',
220
+ Description: 'Garmin GNX Keypad Page Navigation',
221
+ Type: 'Single',
222
+ Complete: true,
223
+ Length: 7,
224
+ Fields: [
225
+ {
226
+ Order: 1,
227
+ Id: 'manufacturerCode',
228
+ Name: 'Manufacturer Code',
229
+ BitLength: 11,
230
+ BitOffset: 0,
231
+ BitStart: 0,
232
+ Match: 229,
233
+ FieldType: 'LOOKUP',
234
+ LookupEnumeration: 'MANUFACTURER_CODE',
235
+ Signed: false
236
+ },
237
+ {
238
+ Order: 2,
239
+ Id: 'reserved',
240
+ Name: 'Reserved',
241
+ BitLength: 2,
242
+ BitOffset: 11,
243
+ BitStart: 3,
244
+ FieldType: 'RESERVED'
245
+ },
246
+ {
247
+ Order: 3,
248
+ Id: 'industryCode',
249
+ Name: 'Industry Code',
250
+ BitLength: 3,
251
+ BitOffset: 13,
252
+ BitStart: 5,
253
+ Match: 4,
254
+ FieldType: 'LOOKUP',
255
+ LookupEnumeration: 'INDUSTRY_CODE',
256
+ Signed: false
257
+ },
258
+ {
259
+ Order: 4,
260
+ Id: 'command',
261
+ Name: 'Command',
262
+ BitLength: 8,
263
+ BitOffset: 16,
264
+ BitStart: 0,
265
+ FieldType: 'NUMBER',
266
+ Resolution: 1,
267
+ Signed: false,
268
+ Match: 0x49
269
+ },
270
+ {
271
+ Order: 5,
272
+ Id: 'productId',
273
+ Name: 'Product ID',
274
+ BitLength: 8,
275
+ BitOffset: 24,
276
+ BitStart: 0,
277
+ FieldType: 'NUMBER',
278
+ Resolution: 1,
279
+ Signed: false
280
+ },
281
+ {
282
+ Order: 6,
283
+ Id: 'unknown1',
284
+ Name: 'Unknown 1',
285
+ BitLength: 8,
286
+ BitOffset: 32,
287
+ BitStart: 0,
288
+ FieldType: 'NUMBER',
289
+ Resolution: 1,
290
+ Signed: false
291
+ },
292
+ {
293
+ Order: 7,
294
+ Id: 'unknown2',
295
+ Name: 'Unknown 2',
296
+ BitLength: 8,
297
+ BitOffset: 40,
298
+ BitStart: 0,
299
+ FieldType: 'NUMBER',
300
+ Resolution: 1,
301
+ Signed: false
302
+ },
303
+ {
304
+ Order: 8,
305
+ Id: 'direction',
306
+ Name: 'Direction',
307
+ BitLength: 8,
308
+ BitOffset: 48,
309
+ BitStart: 0,
310
+ FieldType: 'NUMBER',
311
+ Resolution: 1,
312
+ Signed: false
313
+ }
314
+ ]
315
+ },
316
+ // =========================================================
317
+ // PGN 126720 — Property Command (display, sleep)
318
+ // Byte 2 = 0xe5 distinguishes from heartbeat and race timer
319
+ // =========================================================
320
+ {
321
+ PGN: 126720,
322
+ Id: 'garminGnxKeypadPropertyCommand',
323
+ Description: 'Garmin GNX Keypad Property Command',
324
+ Type: 'Fast',
325
+ Complete: false,
326
+ Fields: [
327
+ {
328
+ Order: 1,
329
+ Id: 'manufacturerCode',
330
+ Name: 'Manufacturer Code',
331
+ BitLength: 11,
332
+ BitOffset: 0,
333
+ BitStart: 0,
334
+ Match: 229,
335
+ FieldType: 'LOOKUP',
336
+ LookupEnumeration: 'MANUFACTURER_CODE',
337
+ Signed: false
338
+ },
339
+ {
340
+ Order: 2,
341
+ Id: 'reserved',
342
+ Name: 'Reserved',
343
+ BitLength: 2,
344
+ BitOffset: 11,
345
+ BitStart: 3,
346
+ FieldType: 'RESERVED'
347
+ },
348
+ {
349
+ Order: 3,
350
+ Id: 'industryCode',
351
+ Name: 'Industry Code',
352
+ BitLength: 3,
353
+ BitOffset: 13,
354
+ BitStart: 5,
355
+ Match: 4,
356
+ FieldType: 'LOOKUP',
357
+ LookupEnumeration: 'INDUSTRY_CODE',
358
+ Signed: false
359
+ },
360
+ {
361
+ Order: 4,
362
+ Id: 'command',
363
+ Name: 'Command',
364
+ BitLength: 8,
365
+ BitOffset: 16,
366
+ BitStart: 0,
367
+ FieldType: 'NUMBER',
368
+ Resolution: 1,
369
+ Signed: false,
370
+ Match: 0xe5
371
+ },
372
+ {
373
+ Order: 5,
374
+ Id: 'payload',
375
+ Name: 'Payload',
376
+ BitLength: 384,
377
+ BitOffset: 24,
378
+ BitStart: 0,
379
+ FieldType: 'Binary data',
380
+ Signed: false
381
+ }
382
+ ]
383
+ },
384
+ // =========================================================
385
+ // PGN 126720 — Heartbeat Ping
386
+ // Byte 2 = 0xe7 distinguishes from property commands
387
+ // =========================================================
388
+ {
389
+ PGN: 126720,
390
+ Id: 'garminGnxKeypadHeartbeat',
391
+ Description: 'Garmin GNX Keypad Heartbeat',
392
+ Type: 'Fast',
393
+ Complete: true,
394
+ Length: 17,
395
+ Fields: [
396
+ {
397
+ Order: 1,
398
+ Id: 'manufacturerCode',
399
+ Name: 'Manufacturer Code',
400
+ BitLength: 11,
401
+ BitOffset: 0,
402
+ BitStart: 0,
403
+ Match: 229,
404
+ FieldType: 'LOOKUP',
405
+ LookupEnumeration: 'MANUFACTURER_CODE',
406
+ Signed: false
407
+ },
408
+ {
409
+ Order: 2,
410
+ Id: 'reserved',
411
+ Name: 'Reserved',
412
+ BitLength: 2,
413
+ BitOffset: 11,
414
+ BitStart: 3,
415
+ FieldType: 'RESERVED'
416
+ },
417
+ {
418
+ Order: 3,
419
+ Id: 'industryCode',
420
+ Name: 'Industry Code',
421
+ BitLength: 3,
422
+ BitOffset: 13,
423
+ BitStart: 5,
424
+ Match: 4,
425
+ FieldType: 'LOOKUP',
426
+ LookupEnumeration: 'INDUSTRY_CODE',
427
+ Signed: false
428
+ },
429
+ {
430
+ Order: 4,
431
+ Id: 'command',
432
+ Name: 'Command',
433
+ BitLength: 8,
434
+ BitOffset: 16,
435
+ BitStart: 0,
436
+ FieldType: 'NUMBER',
437
+ Resolution: 1,
438
+ Signed: false,
439
+ Match: 0xe7
440
+ },
441
+ {
442
+ Order: 5,
443
+ Id: 'payload',
444
+ Name: 'Payload',
445
+ BitLength: 112,
446
+ BitOffset: 24,
447
+ BitStart: 0,
448
+ FieldType: 'Binary data',
449
+ Signed: false
450
+ }
451
+ ]
452
+ }
453
+ ]
454
+ };
@@ -0,0 +1,22 @@
1
+ /// <reference types="node" />
2
+ export declare const PLUGIN_ID = "signalk-garmin-keypad";
3
+ export declare const PGN_SINGLE = 61184;
4
+ export declare const PGN_FAST = 126720;
5
+ export declare const CMD_SELECT_PRESET = 132;
6
+ export declare const CMD_SAVE_PRESET = 133;
7
+ export declare const CMD_PAGE_NAV = 73;
8
+ export declare const PRODUCT_ID = 23;
9
+ export declare const UNK1 = 2;
10
+ export declare const UNK2 = 2;
11
+ export declare const PROP_SLEEP = "gnx_sleep_mode_id";
12
+ export declare const PROP_DISPLAY = "gnx_selected_disp";
13
+ export declare const PROP_DISP_CNT = "gnx_disp_cnt";
14
+ export declare const SLEEP = 0;
15
+ export declare const WAKE = 1;
16
+ export declare const DEFAULT_SRC = 0;
17
+ export declare const DEFAULT_DST = 255;
18
+ export declare const DEFAULT_PRIO = 7;
19
+ export declare const DEFAULT_GROUP_ID: Buffer;
20
+ export declare function buildPropertyHeader(groupId: Buffer): Buffer;
21
+ export declare function parseGroupId(hexStr: string): Buffer;
22
+ export declare const PROPERTY_SEPARATOR: Buffer;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROPERTY_SEPARATOR = exports.parseGroupId = exports.buildPropertyHeader = exports.DEFAULT_GROUP_ID = exports.DEFAULT_PRIO = exports.DEFAULT_DST = exports.DEFAULT_SRC = exports.WAKE = exports.SLEEP = exports.PROP_DISP_CNT = exports.PROP_DISPLAY = exports.PROP_SLEEP = exports.UNK2 = exports.UNK1 = exports.PRODUCT_ID = exports.CMD_PAGE_NAV = exports.CMD_SAVE_PRESET = exports.CMD_SELECT_PRESET = exports.PGN_FAST = exports.PGN_SINGLE = exports.PLUGIN_ID = void 0;
4
+ exports.PLUGIN_ID = 'signalk-garmin-keypad';
5
+ // PGN numbers
6
+ exports.PGN_SINGLE = 61184;
7
+ exports.PGN_FAST = 126720;
8
+ // PGN 61184 command bytes
9
+ exports.CMD_SELECT_PRESET = 0x84;
10
+ exports.CMD_SAVE_PRESET = 0x85;
11
+ exports.CMD_PAGE_NAV = 0x49;
12
+ // Device identification bytes (constant in all PGN 61184 messages)
13
+ exports.PRODUCT_ID = 0x17;
14
+ exports.UNK1 = 0x02;
15
+ exports.UNK2 = 0x02;
16
+ // PGN 126720 property names
17
+ exports.PROP_SLEEP = 'gnx_sleep_mode_id';
18
+ exports.PROP_DISPLAY = 'gnx_selected_disp';
19
+ exports.PROP_DISP_CNT = 'gnx_disp_cnt';
20
+ // Sleep/wake values
21
+ exports.SLEEP = 0;
22
+ exports.WAKE = 1;
23
+ // N2K defaults
24
+ exports.DEFAULT_SRC = 0;
25
+ exports.DEFAULT_DST = 255;
26
+ exports.DEFAULT_PRIO = 7;
27
+ // Default GNX group ID — the 4-byte binding token shared by all devices in a group.
28
+ // Configured during Garmin group setup; persists in device NVM. Any keypad emulation
29
+ // must use the group ID that matches the target display group.
30
+ // Bytes 10-13 of any PGN 126720 0xe5 or 0xe7 message on the GNX bus.
31
+ const DEFAULT_GROUP_ID_HEX = '80d99efc';
32
+ exports.DEFAULT_GROUP_ID = Buffer.from(DEFAULT_GROUP_ID_HEX, 'hex');
33
+ // Property command header layout (14 bytes total):
34
+ // [0] 0xe5 — command byte (consumed by canboatjs PGN Match, NOT in Payload)
35
+ // [1-3] 08 0a 0a — protocol version
36
+ // [4-7] 05 01 03 0d — shared device field
37
+ // [8-11] [groupId] — GROUP ID: 4-byte binding token for this GNX group
38
+ // [12-13] 08 1f — message subtype (property commands)
39
+ function buildPropertyHeader(groupId) {
40
+ return Buffer.from([
41
+ 0xe5, 0x08, 0x0a, 0x0a, 0x05, 0x01,
42
+ 0x03, 0x0d, groupId[0], groupId[1], groupId[2], groupId[3],
43
+ 0x08, 0x1f
44
+ ]);
45
+ }
46
+ exports.buildPropertyHeader = buildPropertyHeader;
47
+ function parseGroupId(hexStr) {
48
+ const cleaned = hexStr.replace(/[^0-9a-fA-F]/g, '');
49
+ if (cleaned.length !== 8) {
50
+ throw new Error(`groupId must be exactly 4 bytes (8 hex digits), got: "${hexStr}"`);
51
+ }
52
+ return Buffer.from(cleaned, 'hex');
53
+ }
54
+ exports.parseGroupId = parseGroupId;
55
+ exports.PROPERTY_SEPARATOR = Buffer.from([0x23, 0x09, 0x01]);
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/').default
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "signalk-garmin-keypad-plugin",
3
+ "version": "1.0.2",
4
+ "description": "Signal K plugin for GNX Keypad control on NMEA 2000",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "build": "tsc && npm run build:webapp",
8
+ "build:plugin": "tsc",
9
+ "build:webapp": "cd webapp && npm run build",
10
+ "watch": "tsc -w",
11
+ "test": "tsc && mocha --exit"
12
+ },
13
+ "keywords": [
14
+ "signalk-node-server-plugin",
15
+ "signalk-category-nmea-2000",
16
+ "signalk-embeddable-webapp"
17
+ ],
18
+ "signalk": {
19
+ "displayName": "GNX Keypad"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "dependencies": {
23
+ "@signalk/server-api": "^1.39.0"
24
+ },
25
+ "devDependencies": {
26
+ "@canboat/canboatjs": "^3.3.0",
27
+ "@types/node": "^14.14.10",
28
+ "chai": "^4.1.2",
29
+ "mocha": "^11.7.5",
30
+ "typescript": "^4.1.2"
31
+ }
32
+ }
package/public/183.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunksignalk_garmin_keypad_webapp=self.webpackChunksignalk_garmin_keypad_webapp||[]).push([[183],{439(e,n,t){t.d(n,{A:()=>c});var r=t(601),a=t.n(r),o=t(314),i=t.n(o)()(a());i.push([e.id,"/* GNX Keypad — dark theme matching the physical device */\n\n.keypad-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100%;\n padding: 20px;\n box-sizing: border-box;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #d0d0d0;\n}\n\n.keypad-body {\n width: 260px;\n background: #1a1a1a;\n border-radius: 20px;\n padding: 20px 16px;\n box-shadow:\n 0 4px 24px rgba(0, 0, 0, 0.5),\n inset 0 1px 0 rgba(255, 255, 255, 0.04);\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.keypad-loading {\n color: #888;\n padding: 40px;\n text-align: center;\n}\n\n/* Status bar */\n.status-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: #888;\n padding: 0 4px;\n min-height: 20px;\n}\n\n.status-label {\n flex: 1;\n font-weight: 700;\n font-size: 15px;\n}\n\n.feedback {\n color: #82b1ff;\n font-size: 11px;\n animation: fadeIn 0.15s ease;\n}\n\n.error {\n color: #ff5252;\n font-size: 11px;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Generic button */\n.keypad-btn {\n background: #2a2a2a;\n border: 1px solid #3a3a3a;\n color: #d0d0d0;\n cursor: pointer;\n font-family: inherit;\n font-size: 14px;\n transition: all 0.15s ease;\n user-select: none;\n -webkit-user-select: none;\n touch-action: manipulation;\n}\n\n.keypad-btn:hover {\n background: #333;\n border-color: #4a4a4a;\n}\n\n.keypad-btn:active {\n background: #222;\n transform: scale(0.96);\n}\n\n/* Power row */\n.power-row {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 0 25px;\n margin-bottom: 32px;\n}\n\n.power-btn {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #2a2a2a;\n}\n\n.power-btn:hover {\n background: #383838;\n}\n\n/* Preset grid */\n.preset-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n padding: 0 10px;\n margin-bottom: 32px;\n}\n\n.preset-btn {\n width: 100%;\n aspect-ratio: 1;\n border-radius: 50%;\n font-size: 20px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 80px;\n max-height: 80px;\n margin: 0 auto;\n}\n\n.preset-btn.long-press {\n background: #7a5c00 !important;\n border-color: #b8860b !important;\n color: #ffd54f !important;\n box-shadow: 0 0 12px rgba(255, 193, 7, 0.3);\n}\n\n/* Control rows (display selector, page nav) */\n.control-row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 0 10px;\n}\n\n/* Arrow buttons */\n.arrow-btn {\n width: 62px;\n height: 53px;\n border-radius: 11px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Center icon in control rows */\n.row-icon {\n width: 62px;\n height: 53px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n}\n\n\n",""]);const c=i},314(e){e.exports=function(e){var n=[];return n.toString=function(){return this.map(function(n){var t="",r=void 0!==n[5];return n[4]&&(t+="@supports (".concat(n[4],") {")),n[2]&&(t+="@media ".concat(n[2]," {")),r&&(t+="@layer".concat(n[5].length>0?" ".concat(n[5]):""," {")),t+=e(n),r&&(t+="}"),n[2]&&(t+="}"),n[4]&&(t+="}"),t}).join("")},n.i=function(e,t,r,a,o){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(r)for(var c=0;c<this.length;c++){var s=this[c][0];null!=s&&(i[s]=!0)}for(var l=0;l<e.length;l++){var d=[].concat(e[l]);r&&i[d[0]]||(void 0!==o&&(void 0===d[5]||(d[1]="@layer".concat(d[5].length>0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=o),t&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=t):d[2]=t),a&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=a):d[4]="".concat(a)),n.push(d))}},n}},601(e){e.exports=function(e){return e[1]}},72(e){var n=[];function t(e){for(var t=-1,r=0;r<n.length;r++)if(n[r].identifier===e){t=r;break}return t}function r(e,r){for(var o={},i=[],c=0;c<e.length;c++){var s=e[c],l=r.base?s[0]+r.base:s[0],d=o[l]||0,p="".concat(l," ").concat(d);o[l]=d+1;var u=t(p),f={css:s[1],media:s[2],sourceMap:s[3],supports:s[4],layer:s[5]};if(-1!==u)n[u].references++,n[u].updater(f);else{var m=a(f,r);r.byIndex=c,n.splice(c,0,{identifier:p,updater:m,references:1})}i.push(p)}return i}function a(e,n){var t=n.domAPI(n);return t.update(e),function(n){if(n){if(n.css===e.css&&n.media===e.media&&n.sourceMap===e.sourceMap&&n.supports===e.supports&&n.layer===e.layer)return;t.update(e=n)}else t.remove()}}e.exports=function(e,a){var o=r(e=e||[],a=a||{});return function(e){e=e||[];for(var i=0;i<o.length;i++){var c=t(o[i]);n[c].references--}for(var s=r(e,a),l=0;l<o.length;l++){var d=t(o[l]);0===n[d].references&&(n[d].updater(),n.splice(d,1))}o=s}}},659(e){var n={};e.exports=function(e,t){var r=function(e){if(void 0===n[e]){var t=document.querySelector(e);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}n[e]=t}return n[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(t)}},540(e){e.exports=function(e){var n=document.createElement("style");return e.setAttributes(n,e.attributes),e.insert(n,e.options),n}},56(e,n,t){e.exports=function(e){var n=t.nc;n&&e.setAttribute("nonce",n)}},825(e){e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var n=e.insertStyleElement(e);return{update:function(t){!function(e,n,t){var r="";t.supports&&(r+="@supports (".concat(t.supports,") {")),t.media&&(r+="@media ".concat(t.media," {"));var a=void 0!==t.layer;a&&(r+="@layer".concat(t.layer.length>0?" ".concat(t.layer):""," {")),r+=t.css,a&&(r+="}"),t.media&&(r+="}"),t.supports&&(r+="}");var o=t.sourceMap;o&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),n.styleTagTransform(r,e,n.options)}(n,e,t)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)}}}},113(e){e.exports=function(e,n){if(n.styleSheet)n.styleSheet.cssText=e;else{for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(document.createTextNode(e))}}},183(e,n,t){t.r(n),t.d(n,{default:()=>E});var r=t(403),a=t.n(r);const o="/plugins/signalk-garmin-keypad";async function i(e,n){const t=await fetch(`${o}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(n)});if(!t.ok){const e=await t.json();throw new Error(e.error||"Request failed")}}var c=t(72),s=t.n(c),l=t(825),d=t.n(l),p=t(659),u=t.n(p),f=t(56),m=t.n(f),h=t(540),y=t.n(h),g=t(113),x=t.n(g),b=t(439),v={};function w({index:e,onSelect:n,onSave:t}){const[o,i]=(0,r.useState)(!1),c=(0,r.useRef)(null);return a().createElement("button",{className:"keypad-btn preset-btn "+(o?"long-press":""),onPointerDown:()=>{i(!1),c.current=setTimeout(()=>{i(!0),t()},500)},onPointerUp:()=>{c.current&&(clearTimeout(c.current),c.current=null),o||n(),i(!1)},onPointerLeave:()=>{c.current&&(clearTimeout(c.current),c.current=null),i(!1)}},e+1)}function k(){const[e,n]=(0,r.useState)(null),[t,c]=(0,r.useState)(null),[s,l]=(0,r.useState)(null),d=e=>{l(e),setTimeout(()=>l(null),1e3)},p=e=>{const n=e instanceof Error?e.message:"Unknown error";c(n),setTimeout(()=>c(null),3e3)},u=(0,r.useCallback)(async()=>{try{const e=await async function(){return(await fetch(`${o}/state`,{credentials:"include"})).json()}();n(e),c(null)}catch(e){p(e)}},[]);(0,r.useEffect)(()=>{u();const e=setInterval(u,5e3);return()=>clearInterval(e)},[u]);const f=async e=>{try{const t=await async function(e){const n=await fetch(`${o}/display/cycle`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({direction:e})});if(!n.ok){const e=await n.json();throw new Error(e.error||"Request failed")}return n.json()}(e);n(e=>e?{...e,activeDisplay:t.displayIndex}:e),d(`Display ${t.displayIndex+1}`)}catch(e){p(e)}},m=async e=>{try{await async function(e){await i("/page",{direction:e})}(e),d(`Page ${e}`)}catch(e){p(e)}};return e?a().createElement("div",{className:"keypad-container"},a().createElement("div",{className:"keypad-body"},a().createElement("div",{className:"status-bar"},a().createElement("span",{className:"status-label"},"GNX Keypad"),s&&a().createElement("span",{className:"feedback"},s),t&&a().createElement("span",{className:"error"},t)),a().createElement("div",{className:"power-row"},a().createElement("button",{className:"keypad-btn power-btn",onClick:()=>{(async e=>{try{await async function(e){await i("/power",{action:e})}(e),n(n=>n?{...n,sleeping:"sleep"===e}:n),d("sleep"===e?"Sleep":"Wake")}catch(e){p(e)}})(e?.sleeping?"wake":"sleep")}},a().createElement("svg",{viewBox:"0 0 24 24",width:"20",height:"20"},a().createElement("line",{x1:"12",y1:"3",x2:"12",y2:"12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"}),a().createElement("path",{d:"M6.3 7.7a8 8 0 1 0 11.4 0",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})))),a().createElement("div",{className:"preset-grid"},[0,1,2,3].map(e=>a().createElement(w,{key:e,index:e,onSelect:()=>(async e=>{try{await async function(e){await i("/preset/select",{index:e})}(e),d(`Preset ${e+1}`)}catch(e){p(e)}})(e),onSave:()=>(async e=>{try{await async function(e){await i("/preset/save",{index:e})}(e),d(`Saved preset ${e+1}`)}catch(e){p(e)}})(e)}))),a().createElement("div",{className:"control-row display-row"},a().createElement("button",{className:"keypad-btn arrow-btn",onClick:()=>f("up")},a().createElement("svg",{viewBox:"0 0 24 24",width:"25",height:"25"},a().createElement("path",{d:"M12 8l-6 6h12z",fill:"currentColor"}))),a().createElement("div",{className:"row-icon"},a().createElement("svg",{viewBox:"0 0 24 24",width:"28",height:"28"},a().createElement("rect",{x:"2",y:"3",width:"20",height:"14",rx:"2",fill:"none",stroke:"currentColor",strokeWidth:"2"}),a().createElement("line",{x1:"8",y1:"21",x2:"16",y2:"21",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"}),a().createElement("line",{x1:"12",y1:"17",x2:"12",y2:"21",stroke:"currentColor",strokeWidth:"2"}))),a().createElement("button",{className:"keypad-btn arrow-btn",onClick:()=>f("down")},a().createElement("svg",{viewBox:"0 0 24 24",width:"25",height:"25"},a().createElement("path",{d:"M12 16l6-6H6z",fill:"currentColor"})))),a().createElement("div",{className:"control-row page-row"},a().createElement("button",{className:"keypad-btn arrow-btn",onClick:()=>m("next")},a().createElement("svg",{viewBox:"0 0 24 24",width:"25",height:"25"},a().createElement("path",{d:"M12 8l-6 6h12z",fill:"currentColor"}))),a().createElement("div",{className:"row-icon"},a().createElement("svg",{viewBox:"0 0 24 24",width:"28",height:"28"},a().createElement("path",{d:"M6 2h9l5 5v15H6z",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinejoin:"round"}),a().createElement("path",{d:"M15 2v5h5",fill:"none",stroke:"currentColor",strokeWidth:"2"}))),a().createElement("button",{className:"keypad-btn arrow-btn",onClick:()=>m("previous")},a().createElement("svg",{viewBox:"0 0 24 24",width:"25",height:"25"},a().createElement("path",{d:"M12 16l6-6H6z",fill:"currentColor"})))))):a().createElement("div",{className:"keypad-container"},a().createElement("div",{className:"keypad-loading"},"Loading..."))}function E(){return a().createElement(k,null)}v.styleTagTransform=x(),v.setAttributes=m(),v.insert=u().bind(null,"head"),v.domAPI=d(),v.insertStyleElement=y(),s()(b.A,v),b.A&&b.A.locals&&b.A.locals}}]);
package/public/main.js ADDED
@@ -0,0 +1 @@
1
+ (()=>{"use strict";var r={221(){}},t={};function e(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}};return r[o](a,a.exports,e),a.exports}e.m=r,e.c=t,e.o=(r,t)=>Object.prototype.hasOwnProperty.call(r,t),(()=>{e.S={};var r={},t={};e.I=(o,n)=>{n||(n=[]);var a=t[o];if(a||(a=t[o]={}),!(n.indexOf(a)>=0)){if(n.push(a),r[o])return r[o];e.o(e.S,o)||(e.S[o]={}),e.S[o];var s=[];return r[o]=s.length?Promise.all(s).then(()=>r[o]=1):1}}})(),e(221)})();
@@ -0,0 +1 @@
1
+ var signalk_garmin_keypad;(()=>{"use strict";var e,r,t,a,n,o,i,l,u,d,s,p,f,c,h,v={254(e,r,t){var a={"./AppPanel":()=>t.e(183).then(()=>()=>t(183))},n=(e,r)=>(t.R=r,r=t.o(a,e)?a[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var a="default",n=t.S[a];if(n&&n!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[a]=e,t.I(a,r)}};t.d(r,{get:()=>n,init:()=>o})}},g={};function m(e){var r=g[e];if(void 0!==r)return r.exports;var t=g[e]={id:e,exports:{}};return v[e](t,t.exports,m),t.exports}m.m=v,m.c=g,m.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return m.d(r,{a:r}),r},m.d=(e,r)=>{for(var t in r)m.o(r,t)&&!m.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},m.f={},m.e=e=>Promise.all(Object.keys(m.f).reduce((r,t)=>(m.f[t](e,r),r),[])),m.u=e=>e+".js",m.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-garmin-keypad-webapp:",m.l=(t,a,n,o)=>{if(e[t])e[t].push(a);else{var i,l;if(void 0!==n)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var s=u[d];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+n){i=s;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",m.nc&&i.setAttribute("nonce",m.nc),i.setAttribute("data-webpack",r+n),i.src=t),e[t]=[a];var p=(r,a)=>{i.onerror=i.onload=null,clearTimeout(f);var n=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach(e=>e(a)),r)return r(a)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=p.bind(null,i.onerror),i.onload=p.bind(null,i.onload),l&&document.head.appendChild(i)}},m.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{m.S={};var e={},r={};m.I=(t,a)=>{a||(a=[]);var n=r[t];if(n||(n=r[t]={}),!(a.indexOf(n)>=0)){if(a.push(n),e[t])return e[t];m.o(m.S,t)||(m.S[t]={}),m.S[t];var o=[];return e[t]=o.length?Promise.all(o).then(()=>e[t]=1):1}}})(),m.p="/signalk-garmin-keypad/",t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),a=t[1]?r(t[1]):[];return t[2]&&(a.length++,a.push.apply(a,r(t[2]))),t[3]&&(a.push([]),a.push.apply(a,r(t[3]))),a},a=(e,r)=>{e=t(e),r=t(r);for(var a=0;;){if(a>=e.length)return a<r.length&&"u"!=(typeof r[a])[0];var n=e[a],o=(typeof n)[0];if(a>=r.length)return"u"==o;var i=r[a],l=(typeof i)[0];if(o!=l)return"o"==o&&"n"==l||"s"==l||"u"==o;if("o"!=o&&"u"!=o&&n!=i)return n<i;a++}},n=(e,r)=>e&&m.o(e,r),o=e=>(e.loaded=1,e.get()),i=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),l=(e,r,t)=>{var n=t?i(e[r]):e[r];return Object.keys(n).reduce((e,r)=>!e||!n[e].loaded&&a(e,r)?r:e,0)},u=e=>{throw new Error(e)},d=(e,r,t)=>t?t():((e,r)=>u("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),s=(e=>function(r,t,a,n,o){var i=m.I(r);return i&&i.then&&!a?i.then(e.bind(e,r,m.S[r],t,!1,n,o)):e(r,m.S[r],t,a,n)})((e,r,t,a,i)=>{if(!n(r,t))return d(e,t,i);var u=l(r,t,a);return o(r[t][u])}),p={},f={403:()=>s("default","react",!1)},c={183:[403]},h={},m.f.consumes=(e,r)=>{m.o(c,e)&&c[e].forEach(e=>{if(m.o(p,e))return r.push(p[e]);if(!h[e]){var t=r=>{p[e]=0,m.m[e]=t=>{delete m.c[e],t.exports=r()}};h[e]=!0;var a=r=>{delete p[e],m.m[e]=t=>{throw delete m.c[e],r}};try{var n=f[e]();n.then?r.push(p[e]=n.then(t).catch(a)):t(n)}catch(e){a(e)}}})},(()=>{var e={802:0};m.f.j=(r,t)=>{var a=m.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else{var n=new Promise((t,n)=>a=e[r]=[t,n]);t.push(a[2]=n);var o=m.p+m.u(r),i=new Error;m.l(o,t=>{if(m.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+n+": "+o+")",i.name="ChunkLoadError",i.type=n,i.request=o,a[1](i)}},"chunk-"+r,r)}};var r=(r,t)=>{var a,n,[o,i,l]=t,u=0;if(o.some(r=>0!==e[r])){for(a in i)m.o(i,a)&&(m.m[a]=i[a]);l&&l(m)}for(r&&r(t);u<o.length;u++)n=o[u],m.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunksignalk_garmin_keypad_webapp=self.webpackChunksignalk_garmin_keypad_webapp||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),m.nc=void 0;var b=m(254);signalk_garmin_keypad=b})();
Binary file
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "rootDir": "src",
7
+ "outDir": "./dist",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "allowJs": true
11
+ },
12
+ "include": ["./src/**/*"]
13
+ }