signalk-wakelogger 0.2.0-beta.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 +32 -0
- package/LICENSE +201 -0
- package/README.md +49 -0
- package/SECURITY.md +7 -0
- package/assets/screenshots/plugin-config.png +0 -0
- package/assets/screenshots/plugin-status.png +0 -0
- package/assets/wakelogger.svg +6 -0
- package/dist/config/defaults.d.ts +18 -0
- package/dist/config/defaults.js +30 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/schema.d.ts +43 -0
- package/dist/config/schema.js +47 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +338 -0
- package/dist/index.js.map +1 -0
- package/dist/outbox/database-outbox.d.ts +25 -0
- package/dist/outbox/database-outbox.js +186 -0
- package/dist/outbox/database-outbox.js.map +1 -0
- package/dist/outbox/database-types.d.ts +19 -0
- package/dist/outbox/database-types.js +3 -0
- package/dist/outbox/database-types.js.map +1 -0
- package/dist/outbox/factory.d.ts +12 -0
- package/dist/outbox/factory.js +98 -0
- package/dist/outbox/factory.js.map +1 -0
- package/dist/outbox/file-outbox.d.ts +27 -0
- package/dist/outbox/file-outbox.js +280 -0
- package/dist/outbox/file-outbox.js.map +1 -0
- package/dist/outbox/interface.d.ts +33 -0
- package/dist/outbox/interface.js +3 -0
- package/dist/outbox/interface.js.map +1 -0
- package/dist/pairing/association-client.d.ts +3 -0
- package/dist/pairing/association-client.js +31 -0
- package/dist/pairing/association-client.js.map +1 -0
- package/dist/pairing/credentials.d.ts +46 -0
- package/dist/pairing/credentials.js +168 -0
- package/dist/pairing/credentials.js.map +1 -0
- package/dist/pairing/pairing-client.d.ts +19 -0
- package/dist/pairing/pairing-client.js +102 -0
- package/dist/pairing/pairing-client.js.map +1 -0
- package/dist/signalk/normaliser.d.ts +11 -0
- package/dist/signalk/normaliser.js +119 -0
- package/dist/signalk/normaliser.js.map +1 -0
- package/dist/signalk/paths.d.ts +2 -0
- package/dist/signalk/paths.js +14 -0
- package/dist/signalk/paths.js.map +1 -0
- package/dist/signalk/subscriber.d.ts +2 -0
- package/dist/signalk/subscriber.js +20 -0
- package/dist/signalk/subscriber.js.map +1 -0
- package/dist/telemetry/profile-store.d.ts +7 -0
- package/dist/telemetry/profile-store.js +39 -0
- package/dist/telemetry/profile-store.js.map +1 -0
- package/dist/telemetry/profile.d.ts +32 -0
- package/dist/telemetry/profile.js +94 -0
- package/dist/telemetry/profile.js.map +1 -0
- package/dist/telemetry/sampler.d.ts +14 -0
- package/dist/telemetry/sampler.js +44 -0
- package/dist/telemetry/sampler.js.map +1 -0
- package/dist/telemetry/types.d.ts +73 -0
- package/dist/telemetry/types.js +3 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/transport/adaptive-mode.d.ts +23 -0
- package/dist/transport/adaptive-mode.js +63 -0
- package/dist/transport/adaptive-mode.js.map +1 -0
- package/dist/transport/mqtt-client.d.ts +69 -0
- package/dist/transport/mqtt-client.js +365 -0
- package/dist/transport/mqtt-client.js.map +1 -0
- package/dist/transport/topics.d.ts +10 -0
- package/dist/transport/topics.js +13 -0
- package/dist/transport/topics.js.map +1 -0
- package/dist/trips/state-machine.d.ts +38 -0
- package/dist/trips/state-machine.js +118 -0
- package/dist/trips/state-machine.js.map +1 -0
- package/docs/architecture.md +17 -0
- package/docs/development.md +49 -0
- package/docs/implementation-plan.md +1943 -0
- package/docs/original-plan-gap-audit.md +47 -0
- package/docs/pairing.md +17 -0
- package/docs/release.md +52 -0
- package/docs/telemetry-protocol.md +50 -0
- package/docs/trip-detection.md +7 -0
- package/docs/validation.md +35 -0
- package/package.json +80 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CredentialStore = void 0;
|
|
7
|
+
exports.validateCredentials = validateCredentials;
|
|
8
|
+
exports.fingerprintPairingCode = fingerprintPairingCode;
|
|
9
|
+
exports.shouldExchangePairingCode = shouldExchangePairingCode;
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const profile_1 = require("../telemetry/profile");
|
|
14
|
+
class CredentialStore {
|
|
15
|
+
directory;
|
|
16
|
+
credentialsFile;
|
|
17
|
+
identityFile;
|
|
18
|
+
pairingStateFile;
|
|
19
|
+
constructor(directory) {
|
|
20
|
+
this.directory = directory;
|
|
21
|
+
this.credentialsFile = node_path_1.default.join(directory, 'credentials.json');
|
|
22
|
+
this.identityFile = node_path_1.default.join(directory, 'installation-id');
|
|
23
|
+
this.pairingStateFile = node_path_1.default.join(directory, 'pairing-state.json');
|
|
24
|
+
}
|
|
25
|
+
async installationId() {
|
|
26
|
+
await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
27
|
+
try {
|
|
28
|
+
const value = (await node_fs_1.promises.readFile(this.identityFile, 'utf8')).trim();
|
|
29
|
+
if (/^[0-9a-f-]{36}$/i.test(value))
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (!isMissing(error))
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
const value = (0, node_crypto_1.randomUUID)();
|
|
37
|
+
await atomicWrite(this.identityFile, `${value}\n`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
async load() {
|
|
41
|
+
try {
|
|
42
|
+
const value = JSON.parse(await node_fs_1.promises.readFile(this.credentialsFile, 'utf8'));
|
|
43
|
+
return validateCredentials(value) ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (isMissing(error) || error instanceof SyntaxError)
|
|
47
|
+
return undefined;
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async save(credentials) {
|
|
52
|
+
if (!validateCredentials(credentials))
|
|
53
|
+
throw new Error('Pairing service returned invalid device credentials');
|
|
54
|
+
await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
55
|
+
await atomicWrite(this.credentialsFile, `${JSON.stringify(credentials)}\n`);
|
|
56
|
+
const previousState = await this.loadPairingState();
|
|
57
|
+
await this.savePairingState({
|
|
58
|
+
version: 1,
|
|
59
|
+
lastPairingCodeFingerprint: credentials.pairingCodeFingerprint ?? previousState?.lastPairingCodeFingerprint,
|
|
60
|
+
lastDeviceId: credentials.deviceId
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async lastPairingCodeFingerprint() {
|
|
64
|
+
const credentials = await this.load();
|
|
65
|
+
if (credentials?.pairingCodeFingerprint)
|
|
66
|
+
return credentials.pairingCodeFingerprint;
|
|
67
|
+
return (await this.loadPairingState())?.lastPairingCodeFingerprint;
|
|
68
|
+
}
|
|
69
|
+
async forget() {
|
|
70
|
+
const credentials = await this.load();
|
|
71
|
+
if (!credentials)
|
|
72
|
+
return { forgotten: false };
|
|
73
|
+
const previousState = await this.loadPairingState();
|
|
74
|
+
await this.savePairingState({
|
|
75
|
+
version: 1,
|
|
76
|
+
lastPairingCodeFingerprint: credentials.pairingCodeFingerprint ?? previousState?.lastPairingCodeFingerprint,
|
|
77
|
+
lastDeviceId: credentials.deviceId,
|
|
78
|
+
forgottenAt: Date.now()
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
await node_fs_1.promises.unlink(this.credentialsFile);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (!isMissing(error))
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
return { forgotten: true, deviceId: credentials.deviceId };
|
|
88
|
+
}
|
|
89
|
+
async loadPairingState() {
|
|
90
|
+
try {
|
|
91
|
+
const value = JSON.parse(await node_fs_1.promises.readFile(this.pairingStateFile, 'utf8'));
|
|
92
|
+
if (value.version !== 1)
|
|
93
|
+
return undefined;
|
|
94
|
+
if (value.lastPairingCodeFingerprint !== undefined && !/^[0-9a-f]{64}$/.test(value.lastPairingCodeFingerprint))
|
|
95
|
+
return undefined;
|
|
96
|
+
if (value.lastDeviceId !== undefined && !validId(value.lastDeviceId))
|
|
97
|
+
return undefined;
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (isMissing(error) || error instanceof SyntaxError)
|
|
102
|
+
return undefined;
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async savePairingState(state) {
|
|
107
|
+
await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
108
|
+
await atomicWrite(this.pairingStateFile, `${JSON.stringify(state)}\n`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.CredentialStore = CredentialStore;
|
|
112
|
+
function validateCredentials(value) {
|
|
113
|
+
return value?.version === 1 && validId(value.deviceId) && validId(value.clientId) &&
|
|
114
|
+
typeof value.username === 'string' && value.username.length > 0 && value.username.length <= 256 &&
|
|
115
|
+
typeof value.password === 'string' && value.password.length >= 16 && value.password.length <= 4096 &&
|
|
116
|
+
validHostname(value.mqttHost) && Number.isInteger(value.mqttPort) && value.mqttPort >= 1 && value.mqttPort <= 65535 &&
|
|
117
|
+
typeof value.tls === 'boolean' &&
|
|
118
|
+
Number.isFinite(value.pairedAt) &&
|
|
119
|
+
(value.pairingCodeFingerprint === undefined || /^[0-9a-f]{64}$/.test(value.pairingCodeFingerprint)) &&
|
|
120
|
+
validTelemetryProfile(value.telemetryProfile) && validOutboxBinding(value.outboxBinding) && validAssociationStatus(value);
|
|
121
|
+
}
|
|
122
|
+
function fingerprintPairingCode(code) {
|
|
123
|
+
return (0, node_crypto_1.createHash)('sha256').update(code, 'utf8').digest('hex');
|
|
124
|
+
}
|
|
125
|
+
function shouldExchangePairingCode(credentials, code, lastFingerprint) {
|
|
126
|
+
return typeof code === 'string' && code.length > 0 &&
|
|
127
|
+
(credentials?.pairingCodeFingerprint ?? lastFingerprint) !== fingerprintPairingCode(code);
|
|
128
|
+
}
|
|
129
|
+
function validId(value) { return typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value); }
|
|
130
|
+
function validHostname(value) {
|
|
131
|
+
return typeof value === 'string' && value.length <= 253 && /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(value);
|
|
132
|
+
}
|
|
133
|
+
function validTelemetryProfile(value) {
|
|
134
|
+
if (value === undefined)
|
|
135
|
+
return true;
|
|
136
|
+
if (typeof value !== 'object' || value === null)
|
|
137
|
+
return false;
|
|
138
|
+
if ((0, profile_1.parseTelemetryProfile)(value))
|
|
139
|
+
return true;
|
|
140
|
+
const legacy = value;
|
|
141
|
+
return (legacy.sample_period_ms === undefined || (Number.isInteger(legacy.sample_period_ms) && legacy.sample_period_ms >= 250 && legacy.sample_period_ms <= 60_000)) &&
|
|
142
|
+
(legacy.batch_size === undefined || (Number.isInteger(legacy.batch_size) && legacy.batch_size >= 1 && legacy.batch_size <= 60));
|
|
143
|
+
}
|
|
144
|
+
function validOutboxBinding(value) {
|
|
145
|
+
return value === undefined || (value.version === 1 && (value.backend === 'file' || value.backend === 'database') &&
|
|
146
|
+
Number.isFinite(value.initializedAt) && value.initializedAt >= 0);
|
|
147
|
+
}
|
|
148
|
+
function validAssociationStatus(value) {
|
|
149
|
+
if (value.associationStatusUrl === undefined && value.associationToken === undefined)
|
|
150
|
+
return true;
|
|
151
|
+
return typeof value.associationStatusUrl === 'string' && /^https:\/\//.test(value.associationStatusUrl) && value.associationStatusUrl.length <= 2048 &&
|
|
152
|
+
typeof value.associationToken === 'string' && value.associationToken.length >= 32 && value.associationToken.length <= 512;
|
|
153
|
+
}
|
|
154
|
+
function isMissing(error) { return error?.code === 'ENOENT'; }
|
|
155
|
+
async function atomicWrite(target, contents) {
|
|
156
|
+
const temporary = `${target}.tmp`;
|
|
157
|
+
await node_fs_1.promises.writeFile(temporary, contents, { mode: 0o600 });
|
|
158
|
+
const handle = await node_fs_1.promises.open(temporary, 'r+');
|
|
159
|
+
try {
|
|
160
|
+
await handle.sync();
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
await handle.close();
|
|
164
|
+
}
|
|
165
|
+
await node_fs_1.promises.rename(temporary, target);
|
|
166
|
+
await node_fs_1.promises.chmod(target, 0o600);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/pairing/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DeviceCredentials } from './credentials';
|
|
2
|
+
export declare class PairingError extends Error {
|
|
3
|
+
readonly retryable: boolean;
|
|
4
|
+
readonly status?: number | undefined;
|
|
5
|
+
constructor(message: string, retryable: boolean, status?: number | undefined);
|
|
6
|
+
}
|
|
7
|
+
interface PairingRequestOptions {
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
export interface PairingRetryOptions extends PairingRequestOptions {
|
|
11
|
+
maxAttempts?: number;
|
|
12
|
+
baseDelayMs?: number;
|
|
13
|
+
maxDelayMs?: number;
|
|
14
|
+
onAttempt?: (attempt: number, maxAttempts: number) => void;
|
|
15
|
+
onRetry?: (nextAttempt: number, maxAttempts: number, delayMs: number, error: PairingError) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function pairDevice(apiUrl: string, pairingCode: string, installationId: string, options?: PairingRequestOptions): Promise<DeviceCredentials>;
|
|
18
|
+
export declare function pairDeviceWithRetry(apiUrl: string, pairingCode: string, installationId: string, options?: PairingRetryOptions): Promise<DeviceCredentials>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PairingError = void 0;
|
|
4
|
+
exports.pairDevice = pairDevice;
|
|
5
|
+
exports.pairDeviceWithRetry = pairDeviceWithRetry;
|
|
6
|
+
const credentials_1 = require("./credentials");
|
|
7
|
+
class PairingError extends Error {
|
|
8
|
+
retryable;
|
|
9
|
+
status;
|
|
10
|
+
constructor(message, retryable, status) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.retryable = retryable;
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = 'PairingError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.PairingError = PairingError;
|
|
18
|
+
async function pairDevice(apiUrl, pairingCode, installationId, options = {}) {
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
21
|
+
const abort = () => controller.abort();
|
|
22
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
23
|
+
try {
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await fetch(apiUrl, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
29
|
+
body: JSON.stringify({ code: pairingCode, name: `Signal K ${installationId.slice(0, 8)}` }),
|
|
30
|
+
signal: controller.signal
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
if (options.signal?.aborted)
|
|
35
|
+
throw new PairingError('Pairing cancelled', false);
|
|
36
|
+
const timedOut = controller.signal.aborted;
|
|
37
|
+
throw new PairingError(timedOut ? 'Pairing request timed out' : 'Pairing service is unavailable', true);
|
|
38
|
+
}
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
|
|
41
|
+
const message = response.status === 400
|
|
42
|
+
? 'Pairing code is invalid or expired'
|
|
43
|
+
: `Pairing failed with HTTP ${response.status}`;
|
|
44
|
+
throw new PairingError(message, retryable, response.status);
|
|
45
|
+
}
|
|
46
|
+
const body = await response.json();
|
|
47
|
+
const credentials = {
|
|
48
|
+
version: 1,
|
|
49
|
+
deviceId: body.device?.id,
|
|
50
|
+
clientId: body.credentials?.client_id,
|
|
51
|
+
username: body.credentials?.username,
|
|
52
|
+
password: body.credentials?.password,
|
|
53
|
+
mqttHost: body.credentials?.broker_host,
|
|
54
|
+
mqttPort: body.credentials?.broker_port,
|
|
55
|
+
tls: body.credentials?.tls,
|
|
56
|
+
telemetryProfile: body.credentials?.telemetry_profile,
|
|
57
|
+
associationStatusUrl: body.credentials?.association_status_url,
|
|
58
|
+
associationToken: body.credentials?.association_token,
|
|
59
|
+
pairedAt: Date.now()
|
|
60
|
+
};
|
|
61
|
+
if (!(0, credentials_1.validateCredentials)(credentials))
|
|
62
|
+
throw new PairingError('Pairing response failed validation', false);
|
|
63
|
+
return credentials;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
options.signal?.removeEventListener('abort', abort);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function pairDeviceWithRetry(apiUrl, pairingCode, installationId, options = {}) {
|
|
71
|
+
const maxAttempts = Math.max(1, Math.min(10, options.maxAttempts ?? 6));
|
|
72
|
+
const baseDelayMs = Math.max(1, options.baseDelayMs ?? 2000);
|
|
73
|
+
const maxDelayMs = Math.max(baseDelayMs, options.maxDelayMs ?? 30_000);
|
|
74
|
+
let lastError = new PairingError('Pairing failed', false);
|
|
75
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
76
|
+
options.onAttempt?.(attempt, maxAttempts);
|
|
77
|
+
try {
|
|
78
|
+
return await pairDevice(apiUrl, pairingCode, installationId, { signal: options.signal });
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
lastError = error instanceof PairingError ? error : new PairingError('Pairing service is unavailable', true);
|
|
82
|
+
if (!lastError.retryable || attempt >= maxAttempts || options.signal?.aborted)
|
|
83
|
+
throw lastError;
|
|
84
|
+
const delayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
|
|
85
|
+
options.onRetry?.(attempt + 1, maxAttempts, delayMs, lastError);
|
|
86
|
+
await abortableDelay(delayMs, options.signal);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw lastError;
|
|
90
|
+
}
|
|
91
|
+
function abortableDelay(delayMs, signal) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
if (signal?.aborted) {
|
|
94
|
+
reject(new PairingError('Pairing cancelled', false));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', abort); resolve(); }, delayMs);
|
|
98
|
+
const abort = () => { clearTimeout(timer); reject(new PairingError('Pairing cancelled', false)); };
|
|
99
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=pairing-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing-client.js","sourceRoot":"","sources":["../../src/pairing/pairing-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Delta } from '@signalk/server-api';
|
|
2
|
+
import type { TelemetryDraft, TelemetryField } from '../telemetry/types';
|
|
3
|
+
export declare class TelemetryNormaliser {
|
|
4
|
+
private readonly values;
|
|
5
|
+
private dirty;
|
|
6
|
+
private readonly emitted;
|
|
7
|
+
ingest(delta: Delta, receivedAt?: number): void;
|
|
8
|
+
takeSample(now?: number, fields?: Set<TelemetryField>): TelemetryDraft | undefined;
|
|
9
|
+
private get;
|
|
10
|
+
private trackPresence;
|
|
11
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TelemetryNormaliser = void 0;
|
|
4
|
+
const MS_PER_SECOND = 1000;
|
|
5
|
+
const MPS_TO_KNOTS = 1.9438444924406;
|
|
6
|
+
const EARLIEST_SOURCE_TIME = Date.UTC(2000, 0, 1);
|
|
7
|
+
const MAX_FUTURE_MS = 5 * 60 * MS_PER_SECOND;
|
|
8
|
+
class TelemetryNormaliser {
|
|
9
|
+
values = new Map();
|
|
10
|
+
dirty = false;
|
|
11
|
+
emitted = new Set();
|
|
12
|
+
ingest(delta, receivedAt = Date.now()) {
|
|
13
|
+
for (const update of delta.updates) {
|
|
14
|
+
if (!('values' in update))
|
|
15
|
+
continue;
|
|
16
|
+
const sourceAt = parseSourceTime(update.timestamp, receivedAt);
|
|
17
|
+
for (const pathValue of update.values) {
|
|
18
|
+
if (!isSupportedPath(pathValue.path))
|
|
19
|
+
continue;
|
|
20
|
+
if (pathValue.state?.timedOut || !isValidPathValue(pathValue.path, pathValue.value)) {
|
|
21
|
+
if (this.values.delete(pathValue.path))
|
|
22
|
+
this.dirty = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
this.values.set(pathValue.path, { value: pathValue.value, sourceAt, receivedAt });
|
|
26
|
+
this.dirty = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
takeSample(now = Date.now(), fields = new Set(['position', 'sog', 'cog', 'heading', 'depth', 'apparentWind'])) {
|
|
31
|
+
if (!this.dirty)
|
|
32
|
+
return undefined;
|
|
33
|
+
const position = this.get('navigation.position', now);
|
|
34
|
+
if (!position)
|
|
35
|
+
return undefined;
|
|
36
|
+
this.dirty = false;
|
|
37
|
+
const headingTrue = fields.has('heading') ? this.get('navigation.headingTrue', now) : undefined;
|
|
38
|
+
const headingMagnetic = fields.has('heading') ? this.get('navigation.headingMagnetic', now) : undefined;
|
|
39
|
+
const capturedAt = position.sourceAt ?? now;
|
|
40
|
+
const quality = { timestamp: position.sourceAt !== undefined ? 'source' : 'receipt' };
|
|
41
|
+
const values = {
|
|
42
|
+
lat: position.value.latitude,
|
|
43
|
+
lon: position.value.longitude,
|
|
44
|
+
sog_kn: fields.has('sog') ? convertNumber(this.get('navigation.speedOverGround', now), (v) => v * MPS_TO_KNOTS) : undefined,
|
|
45
|
+
cog_deg: fields.has('cog') ? convertNumber(this.get('navigation.courseOverGroundTrue', now), radiansToDegrees) : undefined,
|
|
46
|
+
heading_deg: convertNumber(headingTrue ?? headingMagnetic, radiansToDegrees),
|
|
47
|
+
heading_reference: headingTrue !== undefined ? 'true' : headingMagnetic !== undefined ? 'magnetic' : undefined,
|
|
48
|
+
depth_m: fields.has('depth') ? this.get('environment.depth.belowTransducer', now)?.value : undefined,
|
|
49
|
+
aws_kn: fields.has('apparentWind') ? convertNumber(this.get('environment.wind.speedApparent', now), (v) => v * MPS_TO_KNOTS) : undefined,
|
|
50
|
+
awa_deg: fields.has('apparentWind') ? convertNumber(this.get('environment.wind.angleApparent', now), radiansToDegrees) : undefined
|
|
51
|
+
};
|
|
52
|
+
const cleared = [];
|
|
53
|
+
this.trackPresence('sog', fields.has('sog'), values.sog_kn !== undefined, cleared);
|
|
54
|
+
this.trackPresence('cog', fields.has('cog'), values.cog_deg !== undefined, cleared);
|
|
55
|
+
this.trackPresence('heading', fields.has('heading'), values.heading_deg !== undefined, cleared);
|
|
56
|
+
this.trackPresence('depth', fields.has('depth'), values.depth_m !== undefined, cleared);
|
|
57
|
+
this.trackPresence('apparentWind', fields.has('apparentWind'), values.aws_kn !== undefined || values.awa_deg !== undefined, cleared);
|
|
58
|
+
removeUndefined(values);
|
|
59
|
+
return { capturedAt, receivedAt: now, values, quality, cleared: cleared.length ? cleared : undefined };
|
|
60
|
+
}
|
|
61
|
+
get(path, now) {
|
|
62
|
+
const value = this.values.get(path);
|
|
63
|
+
return value && now - value.receivedAt <= 30_000 ? value : undefined;
|
|
64
|
+
}
|
|
65
|
+
trackPresence(field, due, present, cleared) {
|
|
66
|
+
if (!due)
|
|
67
|
+
return;
|
|
68
|
+
if (present)
|
|
69
|
+
this.emitted.add(field);
|
|
70
|
+
else if (this.emitted.delete(field))
|
|
71
|
+
cleared.push(field);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.TelemetryNormaliser = TelemetryNormaliser;
|
|
75
|
+
function parseSourceTime(value, receivedAt) {
|
|
76
|
+
if (!value)
|
|
77
|
+
return undefined;
|
|
78
|
+
const parsed = Date.parse(value);
|
|
79
|
+
return Number.isFinite(parsed) && parsed >= EARLIEST_SOURCE_TIME && parsed <= receivedAt + MAX_FUTURE_MS ? parsed : undefined;
|
|
80
|
+
}
|
|
81
|
+
function isSupportedPath(path) {
|
|
82
|
+
return [
|
|
83
|
+
'navigation.position', 'navigation.speedOverGround', 'navigation.courseOverGroundTrue',
|
|
84
|
+
'navigation.headingTrue', 'navigation.headingMagnetic', 'environment.depth.belowTransducer',
|
|
85
|
+
'environment.wind.speedApparent', 'environment.wind.angleApparent'
|
|
86
|
+
].includes(path);
|
|
87
|
+
}
|
|
88
|
+
function isValidPathValue(path, value) {
|
|
89
|
+
if (path === 'navigation.position') {
|
|
90
|
+
const p = value;
|
|
91
|
+
return !!p && finiteBetween(p.latitude, -90, 90) && finiteBetween(p.longitude, -180, 180);
|
|
92
|
+
}
|
|
93
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
94
|
+
return false;
|
|
95
|
+
switch (path) {
|
|
96
|
+
case 'navigation.speedOverGround': return finiteBetween(value, 0, 80);
|
|
97
|
+
case 'environment.depth.belowTransducer': return finiteBetween(value, 0, 12_000);
|
|
98
|
+
case 'environment.wind.speedApparent': return finiteBetween(value, 0, 120);
|
|
99
|
+
default: return Math.abs(value) <= Math.PI * 100;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function finiteBetween(value, min, max) {
|
|
103
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max;
|
|
104
|
+
}
|
|
105
|
+
function radiansToDegrees(value) {
|
|
106
|
+
return ((value * 180 / Math.PI) % 360 + 360) % 360;
|
|
107
|
+
}
|
|
108
|
+
function convertNumber(value, convert) {
|
|
109
|
+
if (value === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const number = typeof value === 'number' ? value : value.value;
|
|
112
|
+
return Math.round(convert(number) * 1_000_000) / 1_000_000;
|
|
113
|
+
}
|
|
114
|
+
function removeUndefined(value) {
|
|
115
|
+
for (const key of Object.keys(value))
|
|
116
|
+
if (value[key] === undefined)
|
|
117
|
+
delete value[key];
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=normaliser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normaliser.js","sourceRoot":"","sources":["../../src/signalk/normaliser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const SIGNALK_PATHS: readonly ["navigation.position", "navigation.speedOverGround", "navigation.courseOverGroundTrue", "navigation.headingTrue", "navigation.headingMagnetic", "environment.depth.belowTransducer", "environment.wind.speedApparent", "environment.wind.angleApparent"];
|
|
2
|
+
export type SignalKPath = (typeof SIGNALK_PATHS)[number];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SIGNALK_PATHS = void 0;
|
|
4
|
+
exports.SIGNALK_PATHS = [
|
|
5
|
+
'navigation.position',
|
|
6
|
+
'navigation.speedOverGround',
|
|
7
|
+
'navigation.courseOverGroundTrue',
|
|
8
|
+
'navigation.headingTrue',
|
|
9
|
+
'navigation.headingMagnetic',
|
|
10
|
+
'environment.depth.belowTransducer',
|
|
11
|
+
'environment.wind.speedApparent',
|
|
12
|
+
'environment.wind.angleApparent'
|
|
13
|
+
];
|
|
14
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/signalk/paths.ts"],"names":[],"mappings":";;;AAAa,QAAA,aAAa,GAAG;IAC3B,qBAAqB;IACrB,4BAA4B;IAC5B,iCAAiC;IACjC,wBAAwB;IACxB,4BAA4B;IAC5B,mCAAmC;IACnC,gCAAgC;IAChC,gCAAgC;CACxB,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subscribeToTelemetry = subscribeToTelemetry;
|
|
4
|
+
const paths_1 = require("./paths");
|
|
5
|
+
function subscribeToTelemetry(app, callback) {
|
|
6
|
+
const unsubscribes = [];
|
|
7
|
+
app.subscriptionmanager.subscribe({
|
|
8
|
+
context: 'vessels.self',
|
|
9
|
+
subscribe: paths_1.SIGNALK_PATHS.map((path) => ({ path: path, policy: 'instant', minPeriod: 100 })),
|
|
10
|
+
sourcePolicy: 'preferred'
|
|
11
|
+
}, unsubscribes, (error) => app.error(`Wake Logger subscription error: ${safeError(error)}`), callback);
|
|
12
|
+
return () => {
|
|
13
|
+
for (const unsubscribe of unsubscribes.splice(0))
|
|
14
|
+
unsubscribe();
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function safeError(error) {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=subscriber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber.js","sourceRoot":"","sources":["../../src/signalk/subscriber.ts"],"names":[],"mappings":";;AAGA,oDAeC;AAjBD,mCAAuC;AAEvC,SAAgB,oBAAoB,CAAC,GAAc,EAAE,QAAgC;IACnF,MAAM,YAAY,GAAiB,EAAE,CAAA;IACrC,GAAG,CAAC,mBAAmB,CAAC,SAAS,CAC/B;QACE,OAAO,EAAE,cAAuB;QAChC,SAAS,EAAE,qBAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAa,EAAE,MAAM,EAAE,SAAkB,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7G,YAAY,EAAE,WAAW;KAC1B,EACD,YAAY,EACZ,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,mCAAmC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAC3E,QAAQ,CACT,CAAA;IACD,OAAO,GAAG,EAAE;QACV,KAAK,MAAM,WAAW,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,WAAW,EAAE,CAAA;IACjE,CAAC,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC/D,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TelemetryProfileStore = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const profile_1 = require("./profile");
|
|
10
|
+
class TelemetryProfileStore {
|
|
11
|
+
target;
|
|
12
|
+
constructor(directory) { this.target = node_path_1.default.join(directory, 'telemetry-profile.json'); }
|
|
13
|
+
async load() {
|
|
14
|
+
try {
|
|
15
|
+
return (0, profile_1.parseTelemetryProfile)(JSON.parse(await node_fs_1.promises.readFile(this.target, 'utf8')));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT' || error instanceof SyntaxError)
|
|
19
|
+
return undefined;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async save(profile) {
|
|
24
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(this.target), { recursive: true, mode: 0o700 });
|
|
25
|
+
const temporary = `${this.target}.tmp`;
|
|
26
|
+
await node_fs_1.promises.writeFile(temporary, `${JSON.stringify(profile)}\n`, { mode: 0o600 });
|
|
27
|
+
const handle = await node_fs_1.promises.open(temporary, 'r+');
|
|
28
|
+
try {
|
|
29
|
+
await handle.sync();
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await handle.close();
|
|
33
|
+
}
|
|
34
|
+
await node_fs_1.promises.rename(temporary, this.target);
|
|
35
|
+
await node_fs_1.promises.chmod(this.target, 0o600);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.TelemetryProfileStore = TelemetryProfileStore;
|
|
39
|
+
//# sourceMappingURL=profile-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-store.js","sourceRoot":"","sources":["../../src/telemetry/profile-store.ts"],"names":[],"mappings":";;;;;;AAAA,qCAAwC;AACxC,0DAA4B;AAC5B,uCAAwE;AAExE,MAAa,qBAAqB;IACf,MAAM,CAAQ;IAC/B,YAAY,SAAiB,IAAI,IAAI,CAAC,MAAM,GAAG,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAA,CAAC,CAAC;IAE/F,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YAAC,OAAO,IAAA,+BAAqB,EAAC,IAAI,CAAC,KAAK,CAAC,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;QAAC,CAAC;QACxF,OAAO,KAAK,EAAE,CAAC;YACb,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,YAAY,WAAW;gBAAE,OAAO,SAAS,CAAA;YACxG,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAyB;QAClC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3E,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,MAAM,MAAM,CAAA;QACtC,MAAM,kBAAE,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC9E,MAAM,MAAM,GAAG,MAAM,kBAAE,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAC7C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACrB,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACtB,CAAC;QACD,MAAM,kBAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACvC,MAAM,kBAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACpC,CAAC;CACF;AAzBD,sDAyBC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NetworkMode, TelemetryField } from './types';
|
|
2
|
+
export interface PathRateProfile {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
normalMs: number;
|
|
5
|
+
constrainedMs: number;
|
|
6
|
+
offlineMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TelemetryProfile {
|
|
9
|
+
v: 1;
|
|
10
|
+
profileId: string;
|
|
11
|
+
revision: number;
|
|
12
|
+
issuedAt: number;
|
|
13
|
+
paths: Record<TelemetryField, PathRateProfile>;
|
|
14
|
+
transport: {
|
|
15
|
+
batchSize: number;
|
|
16
|
+
maxPayloadBytes: number;
|
|
17
|
+
normalBacklogBytesPerSecond: number;
|
|
18
|
+
constrainedBacklogBytesPerSecond: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface ProfileAcknowledgement {
|
|
22
|
+
v: 1;
|
|
23
|
+
profileId: string;
|
|
24
|
+
revision: number;
|
|
25
|
+
status: 'applied' | 'rejected';
|
|
26
|
+
at: number;
|
|
27
|
+
errorCode?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const DEFAULT_TELEMETRY_PROFILE: TelemetryProfile;
|
|
30
|
+
export declare function parseTelemetryProfile(value: unknown): TelemetryProfile | undefined;
|
|
31
|
+
export declare function legacyProfile(samplePeriodMs?: number, batchSize?: number): TelemetryProfile;
|
|
32
|
+
export declare function periodFor(profile: TelemetryProfile, field: TelemetryField, mode: NetworkMode): number;
|