@mxtommy/kip 3.10.0-beta.8 → 3.10.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 +5 -0
- package/README.md +11 -0
- package/images/KipGaugeSample3-1024x508.png +0 -0
- package/package.json +2 -1
- package/plugin/index.js +238 -31
- package/plugin/openApi.json +367 -0
- package/public/3rdpartylicenses.txt +52 -52
- package/public/assets/help-docs/kiosk.md +192 -0
- package/public/assets/help-docs/menu.json +1 -0
- package/public/assets/help-docs/welcome.md +27 -2
- package/public/assets/svg/icons.svg +5 -1
- package/public/chunk-2XB2ZNXV.js +1 -0
- package/public/{chunk-J3WNXGAQ.js → chunk-4N6AW5Y5.js} +1 -1
- package/public/chunk-5NKFZDV5.js +5 -0
- package/public/chunk-AREYGJLO.js +3 -0
- package/public/chunk-B6IRZFL5.js +3 -0
- package/public/chunk-CCEKSCJH.js +8 -0
- package/public/chunk-DSBAZLLN.js +5 -0
- package/public/chunk-DSWRNQDG.js +15 -0
- package/public/{chunk-LRX3XYXK.js → chunk-EOXCM3IV.js} +1 -1
- package/public/chunk-G5U7W6LL.js +1 -0
- package/public/{chunk-7OMETTVK.js → chunk-JHI7SSDT.js} +1 -1
- package/public/{chunk-GJ33QBJ6.js → chunk-KABAIECE.js} +1 -1
- package/public/chunk-LYPFRDZT.js +1 -0
- package/public/chunk-O2GGGUBC.js +2 -0
- package/public/chunk-QZO4362R.js +4 -0
- package/public/chunk-RJDZKEUA.js +1 -0
- package/public/{chunk-TDHAZ7DS.js → chunk-SVI34QP4.js} +1 -1
- package/public/chunk-TSNRNW3D.js +2 -0
- package/public/{chunk-6VFNB64Z.js → chunk-TTNX7JB6.js} +10 -10
- package/public/chunk-WUFURHSA.js +5 -0
- package/public/chunk-X45MUE6N.js +2 -0
- package/public/chunk-YPVFGYWU.js +1 -0
- package/public/chunk-ZGO25KK6.js +2 -0
- package/public/chunk-ZOYXBB55.js +2 -0
- package/public/index.html +1 -1
- package/public/{main-4QLKDUA3.js → main-JN6ENHFX.js} +12 -14
- package/COPILOT.md +0 -362
- package/eslint.config.js +0 -45
- package/kip-plugin/src/index.ts +0 -53
- package/kip-plugin/tsconfig.plugin.json +0 -13
- package/public/chunk-5UDAYYUA.js +0 -1
- package/public/chunk-BZF6OYAF.js +0 -7
- package/public/chunk-FW2LAMAA.js +0 -16
- package/public/chunk-HKUJILH7.js +0 -6
- package/public/chunk-JFDPDIG2.js +0 -2
- package/public/chunk-MXKB5Z6M.js +0 -5
- package/public/chunk-NL52VRFS.js +0 -1
- package/public/chunk-PTADMSJZ.js +0 -1
- package/public/chunk-RL2DBZFR.js +0 -11
- package/public/chunk-T5GXSVMN.js +0 -1
- package/public/chunk-WQP3T7F5.js +0 -1
- package/public/chunk-XCVMMK2G.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# v 3.10.0
|
|
2
|
+
# New Feature
|
|
3
|
+
* Remote Control Plugin: Instantly switch dashboards on any KIP from any KIP (or your phone). Perfect for mast displays, hard‑to‑reach screens, and non‑touch devices. Open Actions → Settings → Remote Control, pick a device, tap a dashboard—done. Enable remote control in Options → Display → Remote Control.
|
|
4
|
+
# Improvements
|
|
5
|
+
* Added Kiosk Mode setup guide to Help
|
|
1
6
|
# v 3.9.0
|
|
2
7
|
# New Feature
|
|
3
8
|
* A new dashboard navigation experience. Introducing our all-new Dashboard sidenav designed for speed. Effortlessly jump between dashboards with a single tap, always knowing exactly where you are thanks to clear highlighting of your current dashboard.
|
package/README.md
CHANGED
|
@@ -122,6 +122,17 @@ For example, Signal K will notify KIP when a water depth or temperature sensor r
|
|
|
122
122
|
## Multiple User Profiles
|
|
123
123
|
If you have different roles on board: captain, skipper, tactician, navigator, engineer—or simply different people with different needs, each can tailor KIP as they wish. The use of profiles also allows you to tie specific configuration arrangements to use cases or device form factors.
|
|
124
124
|
|
|
125
|
+
## Remote Control Other KIP Displays
|
|
126
|
+
Control which dashboard is shown on another KIP instance (e.g., a mast display, hard-to-reach screen, or a non‑touch device) from any KIP, including your phone.
|
|
127
|
+
|
|
128
|
+
Use cases
|
|
129
|
+
- Mast display: change dashboards from the cockpit.
|
|
130
|
+
- Wall/helm screens: toggle dashboards without standing up or reaching for controls.
|
|
131
|
+
- Non‑touch/no input: select dashboards when no keyboard/mouse is connected or touch is not supported/disabled.
|
|
132
|
+
|
|
133
|
+
## Dedicated Fullscreen instrument display (Kiosk Mode)
|
|
134
|
+
Runs KIP on Raspberry Pi as a single application full-screen, suppresses desktop UI and stays on screen like a dedicated instrument display at a fraction of the cost. Read the [Kiosk Mode](https://github.com/mxtommy/Kip/blob/master/src/assets/help-docs/kiosk.md) help file.
|
|
135
|
+
|
|
125
136
|
## Complementary Components
|
|
126
137
|
Typical complementary components you may install (many are often bundled with Signal K distributions):
|
|
127
138
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxtommy/kip",
|
|
3
|
-
"version": "3.10.0
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "An advanced and versatile marine instrumentation package to display Signal K data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"keywords": [
|
|
26
26
|
"signalk-webapp",
|
|
27
27
|
"signalk-category-instruments",
|
|
28
|
+
"signalk-category-notifications",
|
|
28
29
|
"signalk-node-server-plugin",
|
|
29
30
|
"signal k",
|
|
30
31
|
"signalk",
|
package/plugin/index.js
CHANGED
|
@@ -1,50 +1,257 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
const server_api_1 = require("@signalk/server-api");
|
|
37
|
+
const openapi = __importStar(require("./openApi.json"));
|
|
38
|
+
exports.default = (server) => {
|
|
39
|
+
const API_PATHS = {
|
|
40
|
+
DISPLAYS: `/displays`,
|
|
41
|
+
INSTANCE: `/displays/:displayId`,
|
|
42
|
+
ACTIVE_SCREEN: `/displays/:displayId/activeScreen`
|
|
43
|
+
};
|
|
44
|
+
// Helpers
|
|
45
|
+
function getDisplaySelfPath(displayId, suffix) {
|
|
46
|
+
const tail = suffix ? `.${suffix}` : '';
|
|
47
|
+
const want = `displays.${displayId}${tail}`;
|
|
48
|
+
const full = server.getSelfPath(want);
|
|
49
|
+
server.debug(`getDisplaySelfPath: displayId: ${displayId}, suffix: ${suffix}, want=${want}, fullPath=${JSON.stringify(full)}`);
|
|
50
|
+
return full ? full : undefined;
|
|
51
|
+
}
|
|
52
|
+
function getAvailableDisplays() {
|
|
53
|
+
const fullPath = server.getSelfPath('displays');
|
|
54
|
+
server.debug(`getAvailableDisplays: fullPath=${JSON.stringify(fullPath)}`);
|
|
55
|
+
return fullPath ? fullPath : undefined;
|
|
56
|
+
}
|
|
57
|
+
function pathToDotNotation(path) {
|
|
58
|
+
const dottedPath = path.replace(/\//g, '.').replace(/^\./, '');
|
|
59
|
+
server.debug(`pathToDotNotation: input path=${path}, dottedPath=${dottedPath}`);
|
|
60
|
+
return dottedPath;
|
|
61
|
+
}
|
|
62
|
+
function sendOk(res, body) {
|
|
63
|
+
if (body === undefined)
|
|
64
|
+
return res.status(204).end();
|
|
65
|
+
return res.status(200).json(body);
|
|
66
|
+
}
|
|
67
|
+
function sendFail(res, statusCode, message) {
|
|
68
|
+
return res.status(statusCode).json({ state: 'FAILED', statusCode, message });
|
|
30
69
|
}
|
|
31
70
|
const plugin = {
|
|
32
71
|
id: 'kip',
|
|
33
72
|
name: 'KIP',
|
|
34
73
|
description: 'KIP server plugin',
|
|
35
74
|
start: (settings) => {
|
|
36
|
-
|
|
37
|
-
|
|
75
|
+
server.debug(`Starting plugin with settings: ${JSON.stringify(settings)}`);
|
|
76
|
+
server.setPluginStatus(`Starting...`);
|
|
38
77
|
},
|
|
39
78
|
stop: () => {
|
|
40
|
-
|
|
79
|
+
server.debug(`Stopping plugin`);
|
|
80
|
+
const msg = 'Stopped.';
|
|
81
|
+
server.setPluginStatus(msg);
|
|
41
82
|
},
|
|
42
83
|
schema: () => {
|
|
43
84
|
return {
|
|
44
85
|
type: "object",
|
|
45
86
|
properties: {}
|
|
46
87
|
};
|
|
47
|
-
}
|
|
88
|
+
},
|
|
89
|
+
registerWithRouter(router) {
|
|
90
|
+
server.debug(`Registering plugin routes: ${API_PATHS.DISPLAYS}, ${API_PATHS.INSTANCE}, ${API_PATHS.ACTIVE_SCREEN}`);
|
|
91
|
+
// Validate/normalize :displayId where present
|
|
92
|
+
router.param('displayId', (req, res, next, displayId) => {
|
|
93
|
+
if (displayId == null)
|
|
94
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
95
|
+
try {
|
|
96
|
+
let id = String(displayId);
|
|
97
|
+
// Decode percent-encoding if present
|
|
98
|
+
try {
|
|
99
|
+
id = decodeURIComponent(id);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// ignore decode errors, keep original id
|
|
103
|
+
}
|
|
104
|
+
// If someone sent JSON as the path segment, try to recover {"displayId":"..."}
|
|
105
|
+
if (id.trim().startsWith('{')) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(id);
|
|
108
|
+
if (parsed && typeof parsed.displayId === 'string') {
|
|
109
|
+
id = parsed.displayId;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return sendFail(res, 400, 'Invalid displayId format in JSON');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return sendFail(res, 400, 'Invalid displayId JSON');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Basic safety: allow UUID-like strings (alphanum + dash)
|
|
120
|
+
if (!/^[A-Za-z0-9-]+$/.test(id)) {
|
|
121
|
+
return sendFail(res, 400, 'Invalid displayId format');
|
|
122
|
+
}
|
|
123
|
+
req.displayId = id;
|
|
124
|
+
next();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return sendFail(res, 400, 'Missing or invalid displayId parameter');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
router.put(`${API_PATHS.INSTANCE}`, async (req, res) => {
|
|
131
|
+
server.debug(`** PUT ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
|
|
132
|
+
try {
|
|
133
|
+
const dottedPath = pathToDotNotation(req.path);
|
|
134
|
+
server.debug(`Updating SK path ${dottedPath}`);
|
|
135
|
+
server.handleMessage(plugin.id, {
|
|
136
|
+
updates: [
|
|
137
|
+
{
|
|
138
|
+
values: [
|
|
139
|
+
{
|
|
140
|
+
path: dottedPath,
|
|
141
|
+
value: req.body ?? null
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}, server_api_1.SKVersion.v1);
|
|
147
|
+
return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const msg = `HandleMessage failed with errors!`;
|
|
151
|
+
server.setPluginError(msg);
|
|
152
|
+
server.error(`Error in HandleMessage: ${error}`);
|
|
153
|
+
return sendFail(res, 400, error.message);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
router.put(`${API_PATHS.ACTIVE_SCREEN}`, async (req, res) => {
|
|
157
|
+
server.debug(`** PUT ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
|
|
158
|
+
try {
|
|
159
|
+
const dottedPath = pathToDotNotation(req.path);
|
|
160
|
+
server.debug(`Updating SK path ${dottedPath} with body.screenIdx`);
|
|
161
|
+
server.handleMessage(plugin.id, {
|
|
162
|
+
updates: [
|
|
163
|
+
{
|
|
164
|
+
values: [
|
|
165
|
+
{
|
|
166
|
+
path: dottedPath,
|
|
167
|
+
value: req.body.screenIdx !== undefined ? req.body.screenIdx : null
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}, server_api_1.SKVersion.v1);
|
|
173
|
+
return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const msg = `HandleMessage failed with errors!`;
|
|
177
|
+
server.setPluginError(msg);
|
|
178
|
+
server.error(`Error in HandleMessage: ${error}`);
|
|
179
|
+
return sendFail(res, 400, error.message);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
router.get(API_PATHS.DISPLAYS, (req, res) => {
|
|
183
|
+
server.debug(`** GET ${API_PATHS.DISPLAYS}. Params: ${JSON.stringify(req.params)}`);
|
|
184
|
+
try {
|
|
185
|
+
const displays = getAvailableDisplays();
|
|
186
|
+
const items = displays && typeof displays === 'object'
|
|
187
|
+
? Object.entries(displays)
|
|
188
|
+
.filter(([, v]) => v && typeof v === 'object')
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
+
.map(([displayId, v]) => ({
|
|
191
|
+
displayId,
|
|
192
|
+
displayName: v?.value?.displayName ?? null
|
|
193
|
+
}))
|
|
194
|
+
: [];
|
|
195
|
+
server.debug(`getAvailableDisplays returned: ${JSON.stringify(displays)}`);
|
|
196
|
+
server.debug(`Found ${items.length} displays: ${JSON.stringify(items)}`);
|
|
197
|
+
return res.status(200).json(items);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
server.error(`Error reading displays: ${String(error.message || error)}`);
|
|
201
|
+
return sendFail(res, 400, error.message);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
router.get(`${API_PATHS.INSTANCE}`, (req, res) => {
|
|
205
|
+
server.debug(`** GET ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)}`);
|
|
206
|
+
try {
|
|
207
|
+
const displayId = req.displayId;
|
|
208
|
+
if (!displayId) {
|
|
209
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
210
|
+
}
|
|
211
|
+
const node = getDisplaySelfPath(displayId);
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
const screens = node?.value?.screens ?? null;
|
|
214
|
+
if (screens === undefined) {
|
|
215
|
+
return sendFail(res, 404, `Display ${displayId} not found`);
|
|
216
|
+
}
|
|
217
|
+
return sendOk(res, screens);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
server.error(`Error reading display ${req.params?.displayId}: ${String(error.message || error)}`);
|
|
221
|
+
return sendFail(res, 400, error.message);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
router.get(`${API_PATHS.ACTIVE_SCREEN}`, (req, res) => {
|
|
225
|
+
server.debug(`** GET ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)}`);
|
|
226
|
+
try {
|
|
227
|
+
const displayId = req.displayId;
|
|
228
|
+
if (!displayId) {
|
|
229
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
230
|
+
}
|
|
231
|
+
const node = getDisplaySelfPath(displayId, 'activeScreen');
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
+
const idx = node?.value ?? null;
|
|
234
|
+
if (idx === undefined) {
|
|
235
|
+
return sendFail(res, 404, `Active screen for display ${displayId} not found`);
|
|
236
|
+
}
|
|
237
|
+
return sendOk(res, idx);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
server.error(`Error reading activeScreen for ${req.params?.displayId}: ${String(error.message || error)}`);
|
|
241
|
+
return sendFail(res, 400, error.message);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// List all registered routes for debugging
|
|
245
|
+
if (router.stack) {
|
|
246
|
+
router.stack.forEach((layer) => {
|
|
247
|
+
if (layer.route && layer.route.path) {
|
|
248
|
+
server.debug(`Registered route: ${layer.route.stack[0].method.toUpperCase()} ${layer.route.path}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
server.setPluginStatus(`Providing remote display screen control`);
|
|
253
|
+
},
|
|
254
|
+
getOpenApi: () => openapi
|
|
48
255
|
};
|
|
49
256
|
return plugin;
|
|
50
257
|
};
|