edilkamin 1.14.0 → 1.15.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/.github/dependabot.yml +2 -2
- package/.github/workflows/cli-tests.yml +1 -0
- package/.github/workflows/tests.yml +1 -1
- package/dist/cjs/package.json +8 -7
- package/dist/cjs/src/bluetooth-protocol.d.ts +69 -0
- package/dist/cjs/src/bluetooth-protocol.js +85 -1
- package/dist/cjs/src/bluetooth-protocol.test.js +101 -0
- package/dist/cjs/src/bluetooth.d.ts +2 -2
- package/dist/cjs/src/bluetooth.js +7 -2
- package/dist/cjs/src/cli.js +10 -0
- package/dist/cjs/src/library.d.ts +14 -0
- package/dist/cjs/src/library.js +45 -3
- package/dist/cjs/src/library.test.js +22 -3
- package/dist/esm/package.json +8 -7
- package/dist/esm/src/bluetooth-protocol.d.ts +69 -0
- package/dist/esm/src/bluetooth-protocol.js +79 -0
- package/dist/esm/src/bluetooth-protocol.test.js +102 -1
- package/dist/esm/src/bluetooth.d.ts +2 -2
- package/dist/esm/src/bluetooth.js +3 -3
- package/dist/esm/src/cli.js +10 -0
- package/dist/esm/src/library.d.ts +14 -0
- package/dist/esm/src/library.js +43 -2
- package/dist/esm/src/library.test.js +23 -4
- package/package.json +8 -7
- package/src/bluetooth-protocol.test.ts +151 -0
- package/src/bluetooth-protocol.ts +133 -0
- package/src/bluetooth.ts +12 -2
- package/src/cli.ts +19 -0
- package/src/library.test.ts +30 -3
- package/src/library.ts +49 -2
- package/tsconfig.json +4 -3
package/.github/dependabot.yml
CHANGED
|
@@ -5,7 +5,7 @@ updates:
|
|
|
5
5
|
directory: "/"
|
|
6
6
|
schedule:
|
|
7
7
|
interval: "weekly"
|
|
8
|
-
day: "
|
|
8
|
+
day: "friday"
|
|
9
9
|
time: "09:00"
|
|
10
10
|
timezone: "UTC"
|
|
11
11
|
open-pull-requests-limit: 5
|
|
@@ -21,7 +21,7 @@ updates:
|
|
|
21
21
|
directory: "/"
|
|
22
22
|
schedule:
|
|
23
23
|
interval: "weekly"
|
|
24
|
-
day: "
|
|
24
|
+
day: "friday"
|
|
25
25
|
time: "09:00"
|
|
26
26
|
timezone: "UTC"
|
|
27
27
|
open-pull-requests-limit: 5
|
package/dist/cjs/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edilkamin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/cjs/src/index.js",
|
|
6
6
|
"module": "dist/esm/src/index.js",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"cli:debug": "node --inspect --require ts-node/register/transpile-only src/cli.ts",
|
|
33
33
|
"test": "nyc mocha --require ts-node/register src/*.test.ts",
|
|
34
34
|
"test:debug": "nyc mocha --require ts-node/register/transpile-only --inspect src/*.test.ts",
|
|
35
|
+
"test:cjs-consumer": "node -e \"const pkg = require('./dist/cjs/src/index.js'); if (typeof pkg.configure !== 'function') throw new Error('configure export missing');\"",
|
|
35
36
|
"lint:prettier": "prettier --check src docs .github *.json *.md *.mjs",
|
|
36
37
|
"format:prettier": "prettier --write src docs .github *.json *.md *.mjs",
|
|
37
38
|
"lint:eslint": "eslint src",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@eslint/eslintrc": "^3.2.0",
|
|
71
|
-
"@eslint/js": "^
|
|
72
|
+
"@eslint/js": "^10.0.1",
|
|
72
73
|
"@types/mocha": "^10.0.10",
|
|
73
74
|
"@types/node": "^24",
|
|
74
75
|
"@types/pako": "^2.0.4",
|
|
@@ -76,18 +77,18 @@
|
|
|
76
77
|
"@types/web-bluetooth": "^0.0.21",
|
|
77
78
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
78
79
|
"@typescript-eslint/parser": "^8.17.0",
|
|
79
|
-
"esbuild": "^0.
|
|
80
|
-
"eslint": "^
|
|
80
|
+
"esbuild": "^0.28.0",
|
|
81
|
+
"eslint": "^10.0.3",
|
|
81
82
|
"eslint-config-prettier": "^10.1.8",
|
|
82
83
|
"eslint-plugin-prettier": "^5.2.1",
|
|
83
|
-
"eslint-plugin-simple-import-sort": "^
|
|
84
|
+
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
84
85
|
"mocha": "^11.7.5",
|
|
85
|
-
"nyc": "^
|
|
86
|
+
"nyc": "^18.0.0",
|
|
86
87
|
"prettier": "^3.7.4",
|
|
87
88
|
"sinon": "^21.0.1",
|
|
88
89
|
"ts-node": "^10.9.1",
|
|
89
90
|
"typedoc": "^0.28.15",
|
|
90
|
-
"typescript": "^
|
|
91
|
+
"typescript": "^6.0.3"
|
|
91
92
|
},
|
|
92
93
|
"optionalDependencies": {
|
|
93
94
|
"commander": "^14.0.2"
|
|
@@ -32,6 +32,33 @@ export interface ModbusResponse {
|
|
|
32
32
|
/** Whether the response indicates an error */
|
|
33
33
|
isError: boolean;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Operation-tagged BLE response payload emitted by consuming apps.
|
|
37
|
+
*/
|
|
38
|
+
export interface OperationTaggedPayload {
|
|
39
|
+
/** Operation name associated with this response payload */
|
|
40
|
+
operation: string;
|
|
41
|
+
/** Encrypted BLE response payload bytes */
|
|
42
|
+
payload: Uint8Array;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parsed response bound to its originating operation.
|
|
46
|
+
*/
|
|
47
|
+
export interface OperationTaggedResponse<TResponse> {
|
|
48
|
+
/** Operation name associated with this response payload */
|
|
49
|
+
operation: string;
|
|
50
|
+
/** Parsed response data */
|
|
51
|
+
response: TResponse;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* readWifiStatus operation parse result.
|
|
55
|
+
*/
|
|
56
|
+
export interface ReadWifiStatusResponse {
|
|
57
|
+
/** Operation name for this specialization */
|
|
58
|
+
operation: "readWifiStatus";
|
|
59
|
+
/** Whether Wi-Fi status is connected */
|
|
60
|
+
isConnected: boolean;
|
|
61
|
+
}
|
|
35
62
|
/**
|
|
36
63
|
* Calculate CRC16-Modbus checksum.
|
|
37
64
|
*
|
|
@@ -84,6 +111,48 @@ export declare const createPacket: (modbusCommand: Uint8Array) => Promise<Uint8A
|
|
|
84
111
|
* @returns Parsed Modbus response
|
|
85
112
|
*/
|
|
86
113
|
export declare const parseResponse: (encrypted: Uint8Array) => Promise<ModbusResponse>;
|
|
114
|
+
/**
|
|
115
|
+
* Normalize and validate an operation-tagged BLE payload.
|
|
116
|
+
*
|
|
117
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
118
|
+
* @returns Normalized operation-tagged payload
|
|
119
|
+
*/
|
|
120
|
+
export declare const normalizeOperationTaggedPayload: (taggedPayload: OperationTaggedPayload) => OperationTaggedPayload;
|
|
121
|
+
/**
|
|
122
|
+
* Parse an operation-tagged BLE payload with a caller-provided parser.
|
|
123
|
+
*
|
|
124
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
125
|
+
* @param parser - Parser to apply on payload bytes
|
|
126
|
+
* @returns Operation-tagged parsed response
|
|
127
|
+
*/
|
|
128
|
+
export declare const parseOperationTaggedResponse: <TResponse>(taggedPayload: OperationTaggedPayload, parser: (payload: Uint8Array) => TResponse | Promise<TResponse>) => Promise<OperationTaggedResponse<TResponse>>;
|
|
129
|
+
/**
|
|
130
|
+
* Parse an operation-tagged BLE payload as a Modbus response.
|
|
131
|
+
*
|
|
132
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
133
|
+
* @returns Operation-tagged parsed Modbus response
|
|
134
|
+
*/
|
|
135
|
+
export declare const parseModbusOperationResponse: (taggedPayload: OperationTaggedPayload) => Promise<OperationTaggedResponse<ModbusResponse>>;
|
|
136
|
+
/**
|
|
137
|
+
* Parse decrypted payload bytes for readWifiStatus connectivity.
|
|
138
|
+
*
|
|
139
|
+
* Decrypted packets keep protocol metadata in bytes 0-19, so this parser checks
|
|
140
|
+
* string payload from byte 20 onward.
|
|
141
|
+
*
|
|
142
|
+
* @param decryptedPayload - Decrypted response payload bytes
|
|
143
|
+
* @returns true when payload contains STATUS=CONNECTED
|
|
144
|
+
*/
|
|
145
|
+
export declare const parseReadWifiStatusPayload: (decryptedPayload: Uint8Array) => boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Parse operation-tagged readWifiStatus payloads.
|
|
148
|
+
*
|
|
149
|
+
* For protocol packets, 32-byte payloads are decrypted first, then parsed with
|
|
150
|
+
* readWifiStatus payload conventions.
|
|
151
|
+
*
|
|
152
|
+
* @param taggedPayload - Operation name and payload bytes
|
|
153
|
+
* @returns readWifiStatus operation parse result
|
|
154
|
+
*/
|
|
155
|
+
export declare const parseReadWifiStatusResponse: (taggedPayload: OperationTaggedPayload) => Promise<ReadWifiStatusResponse>;
|
|
87
156
|
/**
|
|
88
157
|
* Pre-built Modbus read commands for querying device state.
|
|
89
158
|
* Each command is 6 bytes: [SlaveAddr, FuncCode, RegHi, RegLo, CountHi, CountLo]
|
|
@@ -22,7 +22,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
22
22
|
});
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.parsers = exports.writeCommands = exports.readCommands = exports.parseResponse = exports.createPacket = exports.aesDecrypt = exports.aesEncrypt = exports.crc16Modbus = exports.NOTIFY_CHARACTERISTIC_UUID = exports.WRITE_CHARACTERISTIC_UUID = exports.SERVICE_UUID = void 0;
|
|
25
|
+
exports.parsers = exports.writeCommands = exports.readCommands = exports.parseReadWifiStatusResponse = exports.parseReadWifiStatusPayload = exports.parseModbusOperationResponse = exports.parseOperationTaggedResponse = exports.normalizeOperationTaggedPayload = exports.parseResponse = exports.createPacket = exports.aesDecrypt = exports.aesEncrypt = exports.crc16Modbus = exports.NOTIFY_CHARACTERISTIC_UUID = exports.WRITE_CHARACTERISTIC_UUID = exports.SERVICE_UUID = void 0;
|
|
26
26
|
// =============================================================================
|
|
27
27
|
// BLE Characteristic UUIDs (for consuming apps)
|
|
28
28
|
// =============================================================================
|
|
@@ -285,6 +285,90 @@ const parseResponse = (encrypted) => __awaiter(void 0, void 0, void 0, function*
|
|
|
285
285
|
};
|
|
286
286
|
});
|
|
287
287
|
exports.parseResponse = parseResponse;
|
|
288
|
+
/**
|
|
289
|
+
* Normalize and validate an operation-tagged BLE payload.
|
|
290
|
+
*
|
|
291
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
292
|
+
* @returns Normalized operation-tagged payload
|
|
293
|
+
*/
|
|
294
|
+
const normalizeOperationTaggedPayload = (taggedPayload) => {
|
|
295
|
+
const normalizedOperation = taggedPayload.operation.trim();
|
|
296
|
+
if (normalizedOperation.length === 0) {
|
|
297
|
+
throw new Error("Operation must be a non-empty string");
|
|
298
|
+
}
|
|
299
|
+
if (taggedPayload.payload.length === 0) {
|
|
300
|
+
throw new Error("Payload must not be empty");
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
operation: normalizedOperation,
|
|
304
|
+
payload: taggedPayload.payload,
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
exports.normalizeOperationTaggedPayload = normalizeOperationTaggedPayload;
|
|
308
|
+
/**
|
|
309
|
+
* Parse an operation-tagged BLE payload with a caller-provided parser.
|
|
310
|
+
*
|
|
311
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
312
|
+
* @param parser - Parser to apply on payload bytes
|
|
313
|
+
* @returns Operation-tagged parsed response
|
|
314
|
+
*/
|
|
315
|
+
const parseOperationTaggedResponse = (taggedPayload, parser) => __awaiter(void 0, void 0, void 0, function* () {
|
|
316
|
+
const normalizedPayload = (0, exports.normalizeOperationTaggedPayload)(taggedPayload);
|
|
317
|
+
const response = yield parser(normalizedPayload.payload);
|
|
318
|
+
return {
|
|
319
|
+
operation: normalizedPayload.operation,
|
|
320
|
+
response,
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
exports.parseOperationTaggedResponse = parseOperationTaggedResponse;
|
|
324
|
+
/**
|
|
325
|
+
* Parse an operation-tagged BLE payload as a Modbus response.
|
|
326
|
+
*
|
|
327
|
+
* @param taggedPayload - Operation name and encrypted payload bytes
|
|
328
|
+
* @returns Operation-tagged parsed Modbus response
|
|
329
|
+
*/
|
|
330
|
+
const parseModbusOperationResponse = (taggedPayload) => __awaiter(void 0, void 0, void 0, function* () {
|
|
331
|
+
return (0, exports.parseOperationTaggedResponse)(taggedPayload, exports.parseResponse);
|
|
332
|
+
});
|
|
333
|
+
exports.parseModbusOperationResponse = parseModbusOperationResponse;
|
|
334
|
+
/**
|
|
335
|
+
* Parse decrypted payload bytes for readWifiStatus connectivity.
|
|
336
|
+
*
|
|
337
|
+
* Decrypted packets keep protocol metadata in bytes 0-19, so this parser checks
|
|
338
|
+
* string payload from byte 20 onward.
|
|
339
|
+
*
|
|
340
|
+
* @param decryptedPayload - Decrypted response payload bytes
|
|
341
|
+
* @returns true when payload contains STATUS=CONNECTED
|
|
342
|
+
*/
|
|
343
|
+
const parseReadWifiStatusPayload = (decryptedPayload) => {
|
|
344
|
+
const payloadBytes = decryptedPayload.slice(20);
|
|
345
|
+
const payloadText = String.fromCharCode(...payloadBytes);
|
|
346
|
+
return payloadText.includes("STATUS=CONNECTED");
|
|
347
|
+
};
|
|
348
|
+
exports.parseReadWifiStatusPayload = parseReadWifiStatusPayload;
|
|
349
|
+
/**
|
|
350
|
+
* Parse operation-tagged readWifiStatus payloads.
|
|
351
|
+
*
|
|
352
|
+
* For protocol packets, 32-byte payloads are decrypted first, then parsed with
|
|
353
|
+
* readWifiStatus payload conventions.
|
|
354
|
+
*
|
|
355
|
+
* @param taggedPayload - Operation name and payload bytes
|
|
356
|
+
* @returns readWifiStatus operation parse result
|
|
357
|
+
*/
|
|
358
|
+
const parseReadWifiStatusResponse = (taggedPayload) => __awaiter(void 0, void 0, void 0, function* () {
|
|
359
|
+
const normalizedPayload = (0, exports.normalizeOperationTaggedPayload)(taggedPayload);
|
|
360
|
+
if (normalizedPayload.operation !== "readWifiStatus") {
|
|
361
|
+
throw new Error("Operation must be readWifiStatus");
|
|
362
|
+
}
|
|
363
|
+
const decryptedPayload = normalizedPayload.payload.length === 32
|
|
364
|
+
? yield (0, exports.aesDecrypt)(normalizedPayload.payload)
|
|
365
|
+
: normalizedPayload.payload;
|
|
366
|
+
return {
|
|
367
|
+
operation: "readWifiStatus",
|
|
368
|
+
isConnected: (0, exports.parseReadWifiStatusPayload)(decryptedPayload),
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
exports.parseReadWifiStatusResponse = parseReadWifiStatusResponse;
|
|
288
372
|
// =============================================================================
|
|
289
373
|
// Modbus Read Commands (Function Code 0x03)
|
|
290
374
|
// =============================================================================
|
|
@@ -138,6 +138,107 @@ describe("bluetooth-protocol", () => {
|
|
|
138
138
|
assert_1.strict.ok(typeof parsed.isError === "boolean");
|
|
139
139
|
}));
|
|
140
140
|
});
|
|
141
|
+
describe("operation-tagged helpers", () => {
|
|
142
|
+
it("normalizes operation by trimming whitespace", () => {
|
|
143
|
+
const payload = new Uint8Array([0x01]);
|
|
144
|
+
const normalized = (0, bluetooth_protocol_1.normalizeOperationTaggedPayload)({
|
|
145
|
+
operation: " readState ",
|
|
146
|
+
payload,
|
|
147
|
+
});
|
|
148
|
+
assert_1.strict.equal(normalized.operation, "readState");
|
|
149
|
+
assert_1.strict.deepEqual(normalized.payload, payload);
|
|
150
|
+
});
|
|
151
|
+
it("rejects empty operation", () => {
|
|
152
|
+
assert_1.strict.throws(() => (0, bluetooth_protocol_1.normalizeOperationTaggedPayload)({
|
|
153
|
+
operation: " ",
|
|
154
|
+
payload: new Uint8Array([0x01]),
|
|
155
|
+
}), /Operation must be a non-empty string/);
|
|
156
|
+
});
|
|
157
|
+
it("rejects empty payload", () => {
|
|
158
|
+
assert_1.strict.throws(() => (0, bluetooth_protocol_1.normalizeOperationTaggedPayload)({
|
|
159
|
+
operation: "readState",
|
|
160
|
+
payload: new Uint8Array([]),
|
|
161
|
+
}), /Payload must not be empty/);
|
|
162
|
+
});
|
|
163
|
+
it("parses operation-tagged payload with sync parser", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
164
|
+
const payload = new Uint8Array([0xaa, 0xbb]);
|
|
165
|
+
const parsed = yield (0, bluetooth_protocol_1.parseOperationTaggedResponse)({
|
|
166
|
+
operation: "readState",
|
|
167
|
+
payload,
|
|
168
|
+
}, (value) => value[0] + value[1]);
|
|
169
|
+
assert_1.strict.equal(parsed.operation, "readState");
|
|
170
|
+
assert_1.strict.equal(parsed.response, 0x165);
|
|
171
|
+
}));
|
|
172
|
+
it("parses operation-tagged payload with async parser", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
|
+
const payload = new Uint8Array([0xaa, 0xbb]);
|
|
174
|
+
const parsed = yield (0, bluetooth_protocol_1.parseOperationTaggedResponse)({
|
|
175
|
+
operation: "readState",
|
|
176
|
+
payload,
|
|
177
|
+
}, (value) => __awaiter(void 0, void 0, void 0, function* () { return value.length; }));
|
|
178
|
+
assert_1.strict.equal(parsed.operation, "readState");
|
|
179
|
+
assert_1.strict.equal(parsed.response, 2);
|
|
180
|
+
}));
|
|
181
|
+
it("parseModbusOperationResponse preserves operation and parses payload", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
const payload = yield (0, bluetooth_protocol_1.createPacket)(bluetooth_protocol_1.readCommands.power);
|
|
183
|
+
const taggedPayload = {
|
|
184
|
+
operation: "readPower",
|
|
185
|
+
payload,
|
|
186
|
+
};
|
|
187
|
+
const parsed = yield (0, bluetooth_protocol_1.parseModbusOperationResponse)(taggedPayload);
|
|
188
|
+
const expected = yield (0, bluetooth_protocol_1.parseResponse)(payload);
|
|
189
|
+
assert_1.strict.equal(parsed.operation, "readPower");
|
|
190
|
+
assert_1.strict.deepEqual(parsed.response, expected);
|
|
191
|
+
}));
|
|
192
|
+
describe("parseReadWifiStatusPayload", () => {
|
|
193
|
+
it("returns true when decrypted payload contains STATUS=CONNECTED", () => {
|
|
194
|
+
const payloadText = "STATUS=CONNECTED";
|
|
195
|
+
const payload = new Uint8Array(20 + payloadText.length);
|
|
196
|
+
payload.set(Buffer.from(payloadText, "latin1"), 20);
|
|
197
|
+
assert_1.strict.equal((0, bluetooth_protocol_1.parseReadWifiStatusPayload)(payload), true);
|
|
198
|
+
});
|
|
199
|
+
it("returns false for non-connected status", () => {
|
|
200
|
+
const payloadText = "STATUS=DISCONNECTED";
|
|
201
|
+
const payload = new Uint8Array(20 + payloadText.length);
|
|
202
|
+
payload.set(Buffer.from(payloadText, "latin1"), 20);
|
|
203
|
+
assert_1.strict.equal((0, bluetooth_protocol_1.parseReadWifiStatusPayload)(payload), false);
|
|
204
|
+
});
|
|
205
|
+
it("returns false when status is missing or invalid", () => {
|
|
206
|
+
const payloadText = "NO_STATUS_PRESENT";
|
|
207
|
+
const payload = new Uint8Array(20 + payloadText.length);
|
|
208
|
+
payload.set(Buffer.from(payloadText, "latin1"), 20);
|
|
209
|
+
assert_1.strict.equal((0, bluetooth_protocol_1.parseReadWifiStatusPayload)(payload), false);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe("parseReadWifiStatusResponse", () => {
|
|
213
|
+
it("parses readWifiStatus operation payloads", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
214
|
+
const payloadText = "STATUS=CONNECTED";
|
|
215
|
+
const payload = new Uint8Array(20 + payloadText.length);
|
|
216
|
+
payload.set(Buffer.from(payloadText, "latin1"), 20);
|
|
217
|
+
const parsed = yield (0, bluetooth_protocol_1.parseReadWifiStatusResponse)({
|
|
218
|
+
operation: "readWifiStatus",
|
|
219
|
+
payload,
|
|
220
|
+
});
|
|
221
|
+
assert_1.strict.equal(parsed.operation, "readWifiStatus");
|
|
222
|
+
assert_1.strict.equal(parsed.isConnected, true);
|
|
223
|
+
}));
|
|
224
|
+
it("returns false when readWifiStatus payload is not connected", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
225
|
+
const payloadText = "STATUS=DISCONNECTED";
|
|
226
|
+
const payload = new Uint8Array(20 + payloadText.length);
|
|
227
|
+
payload.set(Buffer.from(payloadText, "latin1"), 20);
|
|
228
|
+
const parsed = yield (0, bluetooth_protocol_1.parseReadWifiStatusResponse)({
|
|
229
|
+
operation: "readWifiStatus",
|
|
230
|
+
payload,
|
|
231
|
+
});
|
|
232
|
+
assert_1.strict.equal(parsed.isConnected, false);
|
|
233
|
+
}));
|
|
234
|
+
it("rejects non-readWifiStatus operations", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
235
|
+
yield assert_1.strict.rejects(() => (0, bluetooth_protocol_1.parseReadWifiStatusResponse)({
|
|
236
|
+
operation: "readState",
|
|
237
|
+
payload: new Uint8Array([0x01]),
|
|
238
|
+
}), /Operation must be readWifiStatus/);
|
|
239
|
+
}));
|
|
240
|
+
});
|
|
241
|
+
});
|
|
141
242
|
describe("readCommands", () => {
|
|
142
243
|
it("all commands are 6 bytes", () => {
|
|
143
244
|
Object.entries(bluetooth_protocol_1.readCommands).forEach(([name, cmd]) => {
|
|
@@ -38,5 +38,5 @@ declare const scanForDevices: () => Promise<DiscoveredDevice[]>;
|
|
|
38
38
|
declare const scanWithOptions: (options: RequestDeviceOptions) => Promise<BluetoothDevice>;
|
|
39
39
|
export { EDILKAMIN_DEVICE_NAME, EDILKAMIN_SERVICE_UUID, isWebBluetoothSupported, scanForDevices, scanWithOptions, };
|
|
40
40
|
export type { DiscoveredDevice } from "./types";
|
|
41
|
-
export { aesDecrypt, aesEncrypt, crc16Modbus, createPacket, NOTIFY_CHARACTERISTIC_UUID, parseResponse, parsers, readCommands, SERVICE_UUID, WRITE_CHARACTERISTIC_UUID, writeCommands, } from "./bluetooth-protocol";
|
|
42
|
-
export type { ModbusResponse } from "./bluetooth-protocol";
|
|
41
|
+
export { aesDecrypt, aesEncrypt, crc16Modbus, createPacket, normalizeOperationTaggedPayload, NOTIFY_CHARACTERISTIC_UUID, parseModbusOperationResponse, parseOperationTaggedResponse, parseReadWifiStatusPayload, parseReadWifiStatusResponse, parseResponse, parsers, readCommands, SERVICE_UUID, WRITE_CHARACTERISTIC_UUID, writeCommands, } from "./bluetooth-protocol";
|
|
42
|
+
export type { ModbusResponse, OperationTaggedPayload, OperationTaggedResponse, ReadWifiStatusResponse, } from "./bluetooth-protocol";
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.writeCommands = exports.WRITE_CHARACTERISTIC_UUID = exports.SERVICE_UUID = exports.readCommands = exports.parsers = exports.parseResponse = exports.NOTIFY_CHARACTERISTIC_UUID = exports.createPacket = exports.crc16Modbus = exports.aesEncrypt = exports.aesDecrypt = exports.scanWithOptions = exports.scanForDevices = exports.isWebBluetoothSupported = exports.EDILKAMIN_SERVICE_UUID = exports.EDILKAMIN_DEVICE_NAME = void 0;
|
|
12
|
+
exports.writeCommands = exports.WRITE_CHARACTERISTIC_UUID = exports.SERVICE_UUID = exports.readCommands = exports.parsers = exports.parseResponse = exports.parseReadWifiStatusResponse = exports.parseReadWifiStatusPayload = exports.parseOperationTaggedResponse = exports.parseModbusOperationResponse = exports.NOTIFY_CHARACTERISTIC_UUID = exports.normalizeOperationTaggedPayload = exports.createPacket = exports.crc16Modbus = exports.aesEncrypt = exports.aesDecrypt = exports.scanWithOptions = exports.scanForDevices = exports.isWebBluetoothSupported = exports.EDILKAMIN_SERVICE_UUID = exports.EDILKAMIN_DEVICE_NAME = void 0;
|
|
13
13
|
const bluetooth_utils_1 = require("./bluetooth-utils");
|
|
14
14
|
/** Device name broadcast by Edilkamin stoves */
|
|
15
15
|
const EDILKAMIN_DEVICE_NAME = "EDILKAMIN_EP";
|
|
@@ -87,7 +87,7 @@ const scanForDevices = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
87
87
|
}
|
|
88
88
|
throw error;
|
|
89
89
|
}
|
|
90
|
-
throw new Error("Unknown error during Bluetooth scan");
|
|
90
|
+
throw new Error("Unknown error during Bluetooth scan", { cause: error });
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
exports.scanForDevices = scanForDevices;
|
|
@@ -111,8 +111,13 @@ Object.defineProperty(exports, "aesDecrypt", { enumerable: true, get: function (
|
|
|
111
111
|
Object.defineProperty(exports, "aesEncrypt", { enumerable: true, get: function () { return bluetooth_protocol_1.aesEncrypt; } });
|
|
112
112
|
Object.defineProperty(exports, "crc16Modbus", { enumerable: true, get: function () { return bluetooth_protocol_1.crc16Modbus; } });
|
|
113
113
|
Object.defineProperty(exports, "createPacket", { enumerable: true, get: function () { return bluetooth_protocol_1.createPacket; } });
|
|
114
|
+
Object.defineProperty(exports, "normalizeOperationTaggedPayload", { enumerable: true, get: function () { return bluetooth_protocol_1.normalizeOperationTaggedPayload; } });
|
|
114
115
|
// Constants
|
|
115
116
|
Object.defineProperty(exports, "NOTIFY_CHARACTERISTIC_UUID", { enumerable: true, get: function () { return bluetooth_protocol_1.NOTIFY_CHARACTERISTIC_UUID; } });
|
|
117
|
+
Object.defineProperty(exports, "parseModbusOperationResponse", { enumerable: true, get: function () { return bluetooth_protocol_1.parseModbusOperationResponse; } });
|
|
118
|
+
Object.defineProperty(exports, "parseOperationTaggedResponse", { enumerable: true, get: function () { return bluetooth_protocol_1.parseOperationTaggedResponse; } });
|
|
119
|
+
Object.defineProperty(exports, "parseReadWifiStatusPayload", { enumerable: true, get: function () { return bluetooth_protocol_1.parseReadWifiStatusPayload; } });
|
|
120
|
+
Object.defineProperty(exports, "parseReadWifiStatusResponse", { enumerable: true, get: function () { return bluetooth_protocol_1.parseReadWifiStatusResponse; } });
|
|
116
121
|
Object.defineProperty(exports, "parseResponse", { enumerable: true, get: function () { return bluetooth_protocol_1.parseResponse; } });
|
|
117
122
|
// Commands
|
|
118
123
|
Object.defineProperty(exports, "parsers", { enumerable: true, get: function () { return bluetooth_protocol_1.parsers; } });
|
package/dist/cjs/src/cli.js
CHANGED
|
@@ -241,6 +241,11 @@ const createProgram = () => {
|
|
|
241
241
|
description: "Retrieve Relax (comfort) mode status",
|
|
242
242
|
getter: (api, jwtToken, mac) => api.getRelax(jwtToken, mac),
|
|
243
243
|
},
|
|
244
|
+
{
|
|
245
|
+
commandName: "getSound",
|
|
246
|
+
description: "Retrieve control beep sound setting",
|
|
247
|
+
getter: (api, jwtToken, mac) => api.getSound(jwtToken, mac),
|
|
248
|
+
},
|
|
244
249
|
{
|
|
245
250
|
commandName: "getChronoMode",
|
|
246
251
|
description: "Retrieve Chrono (scheduled programming) mode status",
|
|
@@ -379,6 +384,11 @@ const createProgram = () => {
|
|
|
379
384
|
description: "Enable/disable Relax mode (1=on, 0=off)",
|
|
380
385
|
setter: (api, jwtToken, mac, value) => api.setRelax(jwtToken, mac, value === 1),
|
|
381
386
|
},
|
|
387
|
+
{
|
|
388
|
+
commandName: "setSound",
|
|
389
|
+
description: "Enable/disable control beep sounds (1=on, 0=off)",
|
|
390
|
+
setter: (api, jwtToken, mac, value) => api.setSound(jwtToken, mac, value === 1),
|
|
391
|
+
},
|
|
382
392
|
{
|
|
383
393
|
commandName: "setStandby",
|
|
384
394
|
description: "Enable/disable Standby mode (1=on, 0=off)",
|
|
@@ -53,6 +53,18 @@ export declare const deriveAirkare: (deviceInfo: DeviceInfoType) => boolean;
|
|
|
53
53
|
* const isRelaxActive = deriveRelax(info);
|
|
54
54
|
*/
|
|
55
55
|
export declare const deriveRelax: (deviceInfo: DeviceInfoType) => boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Derives the sound (control beep) status from an existing DeviceInfo response.
|
|
58
|
+
* This is a pure function that extracts data without API calls.
|
|
59
|
+
*
|
|
60
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
61
|
+
* @returns {boolean} - Whether control beep sounds are enabled.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const info = await api.deviceInfo(token, mac);
|
|
65
|
+
* const isSoundActive = deriveSound(info);
|
|
66
|
+
*/
|
|
67
|
+
export declare const deriveSound: (deviceInfo: DeviceInfoType) => boolean;
|
|
56
68
|
/**
|
|
57
69
|
* Derives the Chrono mode status from an existing DeviceInfo response.
|
|
58
70
|
* This is a pure function that extracts data without API calls.
|
|
@@ -311,6 +323,8 @@ declare const configure: (baseURL?: string) => {
|
|
|
311
323
|
getAirkare: (jwtToken: string, macAddress: string) => Promise<boolean>;
|
|
312
324
|
setRelax: (jwtToken: string, macAddress: string, enabled: boolean) => Promise<unknown>;
|
|
313
325
|
getRelax: (jwtToken: string, macAddress: string) => Promise<boolean>;
|
|
326
|
+
setSound: (jwtToken: string, macAddress: string, enabled: boolean) => Promise<unknown>;
|
|
327
|
+
getSound: (jwtToken: string, macAddress: string) => Promise<boolean>;
|
|
314
328
|
setStandby: (jwtToken: string, macAddress: string, enabled: boolean) => Promise<unknown>;
|
|
315
329
|
getStandby: (jwtToken: string, macAddress: string) => Promise<boolean>;
|
|
316
330
|
setStandbyTime: (jwtToken: string, macAddress: string, minutes: number) => Promise<unknown>;
|
package/dist/cjs/src/library.js
CHANGED
|
@@ -42,7 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.signIn = exports.headers = exports.getSession = exports.createAuthService = exports.configureAmplify = exports.configure = exports.derivePhaseDescription = exports.getPhaseDescription = exports.deriveUsageAnalytics = exports.deriveAlarmHistory = exports.deriveContinueCochleaLoading = exports.createWorkWeekSchedule = exports.setWeekendRange = exports.setWeekdayRange = exports.setScheduleRange = exports.createEmptySchedule = exports.indexToTime = exports.timeToIndex = exports.deriveEasyTimer = exports.deriveChronoMode = exports.deriveRelax = exports.deriveAirkare = void 0;
|
|
45
|
+
exports.signIn = exports.headers = exports.getSession = exports.createAuthService = exports.configureAmplify = exports.configure = exports.derivePhaseDescription = exports.getPhaseDescription = exports.deriveUsageAnalytics = exports.deriveAlarmHistory = exports.deriveContinueCochleaLoading = exports.createWorkWeekSchedule = exports.setWeekendRange = exports.setWeekdayRange = exports.setScheduleRange = exports.createEmptySchedule = exports.indexToTime = exports.timeToIndex = exports.deriveEasyTimer = exports.deriveChronoMode = exports.deriveSound = exports.deriveRelax = exports.deriveAirkare = void 0;
|
|
46
46
|
const assert_1 = require("assert");
|
|
47
47
|
const aws_amplify_1 = require("aws-amplify");
|
|
48
48
|
const amplifyAuth = __importStar(require("aws-amplify/auth"));
|
|
@@ -348,6 +348,46 @@ const getRelax = (baseURL) =>
|
|
|
348
348
|
const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
|
|
349
349
|
return (0, exports.deriveRelax)(info);
|
|
350
350
|
});
|
|
351
|
+
const setSound = (baseURL) =>
|
|
352
|
+
/**
|
|
353
|
+
* Enables or disables control beep sounds.
|
|
354
|
+
*
|
|
355
|
+
* @param {string} jwtToken - The JWT token for authentication.
|
|
356
|
+
* @param {string} macAddress - The MAC address of the device.
|
|
357
|
+
* @param {boolean} enabled - Whether to enable sound.
|
|
358
|
+
* @returns {Promise<unknown>} - A promise that resolves to the command response.
|
|
359
|
+
*/
|
|
360
|
+
(jwtToken, macAddress, enabled) => mqttCommand(baseURL)(jwtToken, macAddress, {
|
|
361
|
+
name: "radio_control_sound",
|
|
362
|
+
value: enabled,
|
|
363
|
+
});
|
|
364
|
+
/**
|
|
365
|
+
* Derives the sound (control beep) status from an existing DeviceInfo response.
|
|
366
|
+
* This is a pure function that extracts data without API calls.
|
|
367
|
+
*
|
|
368
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
369
|
+
* @returns {boolean} - Whether control beep sounds are enabled.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* const info = await api.deviceInfo(token, mac);
|
|
373
|
+
* const isSoundActive = deriveSound(info);
|
|
374
|
+
*/
|
|
375
|
+
const deriveSound = (deviceInfo) => {
|
|
376
|
+
return deviceInfo.nvm.user_parameters.is_sound_active;
|
|
377
|
+
};
|
|
378
|
+
exports.deriveSound = deriveSound;
|
|
379
|
+
const getSound = (baseURL) =>
|
|
380
|
+
/**
|
|
381
|
+
* Retrieves the current sound (control beep) setting.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} jwtToken - The JWT token for authentication.
|
|
384
|
+
* @param {string} macAddress - The MAC address of the device.
|
|
385
|
+
* @returns {Promise<boolean>} - A promise that resolves to the sound status.
|
|
386
|
+
*/
|
|
387
|
+
(jwtToken, macAddress) => __awaiter(void 0, void 0, void 0, function* () {
|
|
388
|
+
const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
|
|
389
|
+
return (0, exports.deriveSound)(info);
|
|
390
|
+
});
|
|
351
391
|
const setStandby = (baseURL) =>
|
|
352
392
|
/**
|
|
353
393
|
* Enables or disables Standby mode.
|
|
@@ -692,7 +732,7 @@ const setChronoComfortTemperature = (baseURL) =>
|
|
|
692
732
|
* await setChronoComfortTemperature(token, mac, 22);
|
|
693
733
|
*/
|
|
694
734
|
(jwtToken, macAddress, temperature) => mqttCommand(baseURL)(jwtToken, macAddress, {
|
|
695
|
-
name: "
|
|
735
|
+
name: "chrono_comfort_temperature",
|
|
696
736
|
value: temperature,
|
|
697
737
|
});
|
|
698
738
|
const setChronoEconomyTemperature = (baseURL) =>
|
|
@@ -710,7 +750,7 @@ const setChronoEconomyTemperature = (baseURL) =>
|
|
|
710
750
|
* await setChronoEconomyTemperature(token, mac, 18);
|
|
711
751
|
*/
|
|
712
752
|
(jwtToken, macAddress, temperature) => mqttCommand(baseURL)(jwtToken, macAddress, {
|
|
713
|
-
name: "
|
|
753
|
+
name: "chrono_economy_temperature",
|
|
714
754
|
value: temperature,
|
|
715
755
|
});
|
|
716
756
|
/**
|
|
@@ -1413,6 +1453,8 @@ const configure = (baseURL = constants_1.API_URL) => ({
|
|
|
1413
1453
|
getAirkare: getAirkare(baseURL),
|
|
1414
1454
|
setRelax: setRelax(baseURL),
|
|
1415
1455
|
getRelax: getRelax(baseURL),
|
|
1456
|
+
setSound: setSound(baseURL),
|
|
1457
|
+
getSound: getSound(baseURL),
|
|
1416
1458
|
setStandby: setStandby(baseURL),
|
|
1417
1459
|
getStandby: getStandby(baseURL),
|
|
1418
1460
|
setStandbyTime: setStandbyTime(baseURL),
|
|
@@ -202,6 +202,8 @@ describe("library", () => {
|
|
|
202
202
|
"getAirkare",
|
|
203
203
|
"setRelax",
|
|
204
204
|
"getRelax",
|
|
205
|
+
"setSound",
|
|
206
|
+
"getSound",
|
|
205
207
|
"setStandby",
|
|
206
208
|
"getStandby",
|
|
207
209
|
"setStandbyTime",
|
|
@@ -308,6 +310,7 @@ describe("library", () => {
|
|
|
308
310
|
standby_waiting_time: 30,
|
|
309
311
|
is_auto: true,
|
|
310
312
|
is_fahrenheit: false,
|
|
313
|
+
is_sound_active: true,
|
|
311
314
|
language: 2,
|
|
312
315
|
},
|
|
313
316
|
},
|
|
@@ -449,6 +452,11 @@ describe("library", () => {
|
|
|
449
452
|
call: (api, token, mac) => api.getRelax(token, mac),
|
|
450
453
|
expectedResult: false,
|
|
451
454
|
},
|
|
455
|
+
{
|
|
456
|
+
method: "getSound",
|
|
457
|
+
call: (api, token, mac) => api.getSound(token, mac),
|
|
458
|
+
expectedResult: true,
|
|
459
|
+
},
|
|
452
460
|
{
|
|
453
461
|
method: "getChronoMode",
|
|
454
462
|
call: (api, token, mac) => api.getChronoMode(token, mac),
|
|
@@ -552,7 +560,7 @@ describe("library", () => {
|
|
|
552
560
|
method: "setChronoComfortTemperature",
|
|
553
561
|
call: (api, token, mac, value) => api.setChronoComfortTemperature(token, mac, value),
|
|
554
562
|
payload: {
|
|
555
|
-
name: "
|
|
563
|
+
name: "chrono_comfort_temperature",
|
|
556
564
|
value: 22,
|
|
557
565
|
},
|
|
558
566
|
},
|
|
@@ -560,7 +568,7 @@ describe("library", () => {
|
|
|
560
568
|
method: "setChronoEconomyTemperature",
|
|
561
569
|
call: (api, token, mac, value) => api.setChronoEconomyTemperature(token, mac, value),
|
|
562
570
|
payload: {
|
|
563
|
-
name: "
|
|
571
|
+
name: "chrono_economy_temperature",
|
|
564
572
|
value: 18,
|
|
565
573
|
},
|
|
566
574
|
},
|
|
@@ -596,6 +604,12 @@ describe("library", () => {
|
|
|
596
604
|
truePayload: { name: "relax_mode", value: true },
|
|
597
605
|
falsePayload: { name: "relax_mode", value: false },
|
|
598
606
|
},
|
|
607
|
+
{
|
|
608
|
+
method: "setSound",
|
|
609
|
+
call: (api, token, mac, enabled) => api.setSound(token, mac, enabled),
|
|
610
|
+
truePayload: { name: "radio_control_sound", value: true },
|
|
611
|
+
falsePayload: { name: "radio_control_sound", value: false },
|
|
612
|
+
},
|
|
599
613
|
{
|
|
600
614
|
method: "setStandby",
|
|
601
615
|
call: (api, token, mac, enabled) => api.setStandby(token, mac, enabled),
|
|
@@ -1417,7 +1431,7 @@ describe("library", () => {
|
|
|
1417
1431
|
easytimer: { time: 30 },
|
|
1418
1432
|
},
|
|
1419
1433
|
nvm: {
|
|
1420
|
-
user_parameters: {},
|
|
1434
|
+
user_parameters: { is_sound_active: true },
|
|
1421
1435
|
total_counters: {},
|
|
1422
1436
|
service_counters: {},
|
|
1423
1437
|
alarms_log: { number: 0, index: 0, alarms: [] },
|
|
@@ -1434,6 +1448,11 @@ describe("library", () => {
|
|
|
1434
1448
|
const result = (0, library_1.deriveRelax)(mockDeviceInfoForModes);
|
|
1435
1449
|
assert_1.strict.equal(result, false);
|
|
1436
1450
|
});
|
|
1451
|
+
it("should derive Sound status from device info", () => {
|
|
1452
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1453
|
+
const result = (0, library_1.deriveSound)(mockDeviceInfoForModes);
|
|
1454
|
+
assert_1.strict.equal(result, true);
|
|
1455
|
+
});
|
|
1437
1456
|
it("should derive Chrono mode status from device info", () => {
|
|
1438
1457
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1439
1458
|
const result = (0, library_1.deriveChronoMode)(mockDeviceInfoForModes);
|