edilkamin 1.14.1 → 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.
@@ -5,7 +5,7 @@ updates:
5
5
  directory: "/"
6
6
  schedule:
7
7
  interval: "weekly"
8
- day: "monday"
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: "monday"
24
+ day: "friday"
25
25
  time: "09:00"
26
26
  timezone: "UTC"
27
27
  open-pull-requests-limit: 5
@@ -20,6 +20,7 @@ jobs:
20
20
  - run: yarn cli --help
21
21
  - run: yarn build
22
22
  - run: node dist/cjs/src/cli.js --help
23
+ - run: yarn test:cjs-consumer
23
24
  - name: Verify CLI binary
24
25
  run: |
25
26
  test -f dist/cjs/src/cli.js
@@ -20,7 +20,7 @@ jobs:
20
20
  - run: yarn lint
21
21
  - run: yarn build
22
22
  - run: yarn test
23
- - uses: codecov/codecov-action@v5
23
+ - uses: codecov/codecov-action@v6
24
24
  with:
25
25
  files: ./coverage/lcov.info
26
26
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edilkamin",
3
- "version": "1.14.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": "^9.16.0",
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.27.1",
80
- "eslint": "^9.16.0",
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": "^12.1.1",
84
+ "eslint-plugin-simple-import-sort": "^13.0.0",
84
85
  "mocha": "^11.7.5",
85
- "nyc": "^17.1.0",
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": "^5.7.2"
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; } });
@@ -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>;
@@ -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.
@@ -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),
@@ -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);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edilkamin",
3
- "version": "1.14.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": "^9.16.0",
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.27.1",
80
- "eslint": "^9.16.0",
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": "^12.1.1",
84
+ "eslint-plugin-simple-import-sort": "^13.0.0",
84
85
  "mocha": "^11.7.5",
85
- "nyc": "^17.1.0",
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": "^5.7.2"
91
+ "typescript": "^6.0.3"
91
92
  },
92
93
  "optionalDependencies": {
93
94
  "commander": "^14.0.2"