edilkamin 1.10.2 → 1.12.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.
Files changed (52) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/bluetooth-protocol.d.ts +178 -0
  3. package/dist/cjs/src/bluetooth-protocol.js +423 -0
  4. package/dist/cjs/src/bluetooth-protocol.test.d.ts +1 -0
  5. package/dist/cjs/src/bluetooth-protocol.test.js +389 -0
  6. package/dist/cjs/src/bluetooth-utils.js +2 -6
  7. package/dist/cjs/src/bluetooth.d.ts +2 -0
  8. package/dist/cjs/src/bluetooth.js +17 -1
  9. package/dist/cjs/src/cli.js +9 -8
  10. package/dist/cjs/src/index.d.ts +4 -3
  11. package/dist/cjs/src/index.js +14 -1
  12. package/dist/cjs/src/library.d.ts +30 -0
  13. package/dist/cjs/src/library.js +97 -3
  14. package/dist/cjs/src/library.test.js +225 -4
  15. package/dist/cjs/src/mac-utils.d.ts +15 -0
  16. package/dist/cjs/src/mac-utils.js +24 -0
  17. package/dist/cjs/src/mac-utils.test.d.ts +1 -0
  18. package/dist/cjs/src/mac-utils.test.js +41 -0
  19. package/dist/cjs/src/types.d.ts +94 -2
  20. package/dist/cjs/src/types.js +95 -1
  21. package/dist/esm/package.json +1 -1
  22. package/dist/esm/src/bluetooth-protocol.d.ts +178 -0
  23. package/dist/esm/src/bluetooth-protocol.js +415 -0
  24. package/dist/esm/src/bluetooth-protocol.test.d.ts +1 -0
  25. package/dist/esm/src/bluetooth-protocol.test.js +387 -0
  26. package/dist/esm/src/bluetooth-utils.js +2 -6
  27. package/dist/esm/src/bluetooth.d.ts +2 -0
  28. package/dist/esm/src/bluetooth.js +8 -0
  29. package/dist/esm/src/cli.js +9 -8
  30. package/dist/esm/src/index.d.ts +4 -3
  31. package/dist/esm/src/index.js +3 -2
  32. package/dist/esm/src/library.d.ts +30 -0
  33. package/dist/esm/src/library.js +94 -2
  34. package/dist/esm/src/library.test.js +226 -5
  35. package/dist/esm/src/mac-utils.d.ts +15 -0
  36. package/dist/esm/src/mac-utils.js +21 -0
  37. package/dist/esm/src/mac-utils.test.d.ts +1 -0
  38. package/dist/esm/src/mac-utils.test.js +39 -0
  39. package/dist/esm/src/types.d.ts +94 -2
  40. package/dist/esm/src/types.js +89 -1
  41. package/package.json +1 -1
  42. package/src/bluetooth-protocol.test.ts +497 -0
  43. package/src/bluetooth-protocol.ts +524 -0
  44. package/src/bluetooth-utils.ts +3 -7
  45. package/src/bluetooth.ts +21 -0
  46. package/src/cli.ts +9 -8
  47. package/src/index.ts +24 -2
  48. package/src/library.test.ts +325 -4
  49. package/src/library.ts +109 -2
  50. package/src/mac-utils.test.ts +60 -0
  51. package/src/mac-utils.ts +22 -0
  52. package/src/types.ts +144 -1
@@ -0,0 +1,387 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { strict as assert } from "assert";
11
+ import { aesDecrypt, aesEncrypt, crc16Modbus, createPacket, NOTIFY_CHARACTERISTIC_UUID, parseResponse, parsers, readCommands, SERVICE_UUID, WRITE_CHARACTERISTIC_UUID, writeCommands, } from "./bluetooth-protocol";
12
+ describe("bluetooth-protocol", () => {
13
+ describe("constants", () => {
14
+ it("exports SERVICE_UUID", () => {
15
+ assert.equal(SERVICE_UUID, "0000abf0-0000-1000-8000-00805f9b34fb");
16
+ });
17
+ it("exports WRITE_CHARACTERISTIC_UUID", () => {
18
+ assert.equal(WRITE_CHARACTERISTIC_UUID, "0000abf1-0000-1000-8000-00805f9b34fb");
19
+ });
20
+ it("exports NOTIFY_CHARACTERISTIC_UUID", () => {
21
+ assert.equal(NOTIFY_CHARACTERISTIC_UUID, "0000abf2-0000-1000-8000-00805f9b34fb");
22
+ });
23
+ });
24
+ describe("crc16Modbus", () => {
25
+ it("calculates correct CRC for power-on command", () => {
26
+ const command = new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x01]);
27
+ const crc = crc16Modbus(command);
28
+ assert.equal(crc.length, 2);
29
+ // CRC is returned as [crcLo, crcHi]
30
+ assert.ok(crc[0] >= 0 && crc[0] <= 255);
31
+ assert.ok(crc[1] >= 0 && crc[1] <= 255);
32
+ });
33
+ it("returns 2 bytes for any input", () => {
34
+ const testCases = [
35
+ new Uint8Array([0x01, 0x03, 0x05, 0x25, 0x00, 0x01]),
36
+ new Uint8Array([0x01, 0x06, 0x04, 0x40, 0x00, 0x03]),
37
+ new Uint8Array([0x00]),
38
+ new Uint8Array([0xff, 0xff, 0xff]),
39
+ ];
40
+ for (const data of testCases) {
41
+ const crc = crc16Modbus(data);
42
+ assert.equal(crc.length, 2, `CRC for ${data.toString()} should be 2 bytes`);
43
+ }
44
+ });
45
+ it("produces different CRCs for different data", () => {
46
+ const crc1 = crc16Modbus(new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x01]));
47
+ const crc2 = crc16Modbus(new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x00]));
48
+ // CRCs should be different for different data
49
+ assert.ok(crc1[0] !== crc2[0] || crc1[1] !== crc2[1]);
50
+ });
51
+ });
52
+ describe("aesEncrypt/aesDecrypt", () => {
53
+ it("roundtrip returns original data", () => __awaiter(void 0, void 0, void 0, function* () {
54
+ const original = new Uint8Array(32).fill(0x42);
55
+ const encrypted = yield aesEncrypt(original);
56
+ const decrypted = yield aesDecrypt(encrypted);
57
+ assert.deepEqual(decrypted, original);
58
+ }));
59
+ it("produces 32-byte output for 32-byte input", () => __awaiter(void 0, void 0, void 0, function* () {
60
+ const input = new Uint8Array(32);
61
+ const encrypted = yield aesEncrypt(input);
62
+ assert.equal(encrypted.length, 32);
63
+ }));
64
+ it("produces different output for different input", () => __awaiter(void 0, void 0, void 0, function* () {
65
+ const input1 = new Uint8Array(32).fill(0x00);
66
+ const input2 = new Uint8Array(32).fill(0xff);
67
+ const encrypted1 = yield aesEncrypt(input1);
68
+ const encrypted2 = yield aesEncrypt(input2);
69
+ // At least some bytes should be different
70
+ let different = false;
71
+ for (let i = 0; i < 32; i++) {
72
+ if (encrypted1[i] !== encrypted2[i]) {
73
+ different = true;
74
+ break;
75
+ }
76
+ }
77
+ assert.ok(different, "Different inputs should produce different outputs");
78
+ }));
79
+ it("encrypted data is different from plaintext", () => __awaiter(void 0, void 0, void 0, function* () {
80
+ const original = new Uint8Array(32).fill(0xaa);
81
+ const encrypted = yield aesEncrypt(original);
82
+ // Encrypted should not equal original
83
+ let same = true;
84
+ for (let i = 0; i < 32; i++) {
85
+ if (encrypted[i] !== original[i]) {
86
+ same = false;
87
+ break;
88
+ }
89
+ }
90
+ assert.ok(!same, "Encrypted data should differ from original");
91
+ }));
92
+ });
93
+ describe("createPacket", () => {
94
+ it("produces 32-byte encrypted packet", () => __awaiter(void 0, void 0, void 0, function* () {
95
+ const command = readCommands.power;
96
+ const packet = yield createPacket(command);
97
+ assert.equal(packet.length, 32);
98
+ }));
99
+ it("rejects commands not exactly 6 bytes", () => __awaiter(void 0, void 0, void 0, function* () {
100
+ yield assert.rejects(() => createPacket(new Uint8Array([0x01, 0x02])), /must be exactly 6 bytes/);
101
+ }));
102
+ it("rejects empty commands", () => __awaiter(void 0, void 0, void 0, function* () {
103
+ yield assert.rejects(() => createPacket(new Uint8Array([])), /must be exactly 6 bytes/);
104
+ }));
105
+ it("rejects 7-byte commands", () => __awaiter(void 0, void 0, void 0, function* () {
106
+ yield assert.rejects(() => createPacket(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07])), /must be exactly 6 bytes/);
107
+ }));
108
+ it("produces different packets for different commands", () => __awaiter(void 0, void 0, void 0, function* () {
109
+ const packet1 = yield createPacket(writeCommands.setPower(true));
110
+ const packet2 = yield createPacket(writeCommands.setPower(false));
111
+ // Packets should be different (different command bytes)
112
+ let different = false;
113
+ for (let i = 0; i < 32; i++) {
114
+ if (packet1[i] !== packet2[i]) {
115
+ different = true;
116
+ break;
117
+ }
118
+ }
119
+ assert.ok(different, "Different commands should produce different packets");
120
+ }));
121
+ });
122
+ describe("parseResponse", () => {
123
+ it("rejects responses not 32 bytes", () => __awaiter(void 0, void 0, void 0, function* () {
124
+ yield assert.rejects(() => parseResponse(new Uint8Array(16)), /must be exactly 32 bytes/);
125
+ }));
126
+ it("can decrypt and parse an encrypted packet", () => __awaiter(void 0, void 0, void 0, function* () {
127
+ // Create a command packet and try to parse it
128
+ // This tests that encrypt/decrypt work together
129
+ const command = readCommands.power;
130
+ const encrypted = yield createPacket(command);
131
+ // Parse the response (it's not a valid response but should decrypt)
132
+ const parsed = yield parseResponse(encrypted);
133
+ // Should have parsed something
134
+ assert.ok(typeof parsed.slaveAddress === "number");
135
+ assert.ok(typeof parsed.functionCode === "number");
136
+ assert.ok(typeof parsed.isError === "boolean");
137
+ }));
138
+ });
139
+ describe("readCommands", () => {
140
+ it("all commands are 6 bytes", () => {
141
+ Object.entries(readCommands).forEach(([name, cmd]) => {
142
+ assert.equal(cmd.length, 6, `${name} command must be 6 bytes`);
143
+ });
144
+ });
145
+ it("all commands use slave address 0x01", () => {
146
+ Object.entries(readCommands).forEach(([name, cmd]) => {
147
+ assert.equal(cmd[0], 0x01, `${name} should use slave address 0x01`);
148
+ });
149
+ });
150
+ it("all commands use function code 0x03", () => {
151
+ Object.entries(readCommands).forEach(([name, cmd]) => {
152
+ assert.equal(cmd[1], 0x03, `${name} should use function code 0x03`);
153
+ });
154
+ });
155
+ it("power command has correct register address", () => {
156
+ assert.equal(readCommands.power[2], 0x05);
157
+ assert.equal(readCommands.power[3], 0x29);
158
+ });
159
+ it("temperature command has correct register address", () => {
160
+ assert.equal(readCommands.temperature[2], 0x05);
161
+ assert.equal(readCommands.temperature[3], 0x25);
162
+ });
163
+ });
164
+ describe("writeCommands", () => {
165
+ it("setPower(true) produces correct bytes", () => {
166
+ const cmd = writeCommands.setPower(true);
167
+ assert.deepEqual(cmd, new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x01]));
168
+ });
169
+ it("setPower(false) produces correct bytes", () => {
170
+ const cmd = writeCommands.setPower(false);
171
+ assert.deepEqual(cmd, new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x00]));
172
+ });
173
+ it("setTemperature encodes correctly", () => {
174
+ const cmd = writeCommands.setTemperature(21.5);
175
+ // 21.5 * 10 = 215 = 0x00D7
176
+ assert.equal(cmd[0], 0x01); // slave address
177
+ assert.equal(cmd[1], 0x06); // function code
178
+ assert.equal(cmd[2], 0x05); // register hi
179
+ assert.equal(cmd[3], 0x25); // register lo
180
+ assert.equal(cmd[4], 0x00); // value hi
181
+ assert.equal(cmd[5], 0xd7); // value lo
182
+ });
183
+ it("setTemperature handles whole numbers", () => {
184
+ const cmd = writeCommands.setTemperature(20);
185
+ // 20 * 10 = 200 = 0x00C8
186
+ assert.equal(cmd[4], 0x00);
187
+ assert.equal(cmd[5], 0xc8);
188
+ });
189
+ it("setTemperature handles high temperatures", () => {
190
+ const cmd = writeCommands.setTemperature(30);
191
+ // 30 * 10 = 300 = 0x012C
192
+ assert.equal(cmd[4], 0x01);
193
+ assert.equal(cmd[5], 0x2c);
194
+ });
195
+ it("setPowerLevel validates range", () => {
196
+ assert.throws(() => writeCommands.setPowerLevel(0), /must be 1-5/);
197
+ assert.throws(() => writeCommands.setPowerLevel(6), /must be 1-5/);
198
+ assert.doesNotThrow(() => writeCommands.setPowerLevel(1));
199
+ assert.doesNotThrow(() => writeCommands.setPowerLevel(3));
200
+ assert.doesNotThrow(() => writeCommands.setPowerLevel(5));
201
+ });
202
+ it("setPowerLevel produces correct bytes", () => {
203
+ const cmd = writeCommands.setPowerLevel(3);
204
+ assert.equal(cmd[0], 0x01);
205
+ assert.equal(cmd[1], 0x06);
206
+ assert.equal(cmd[2], 0x04);
207
+ assert.equal(cmd[3], 0x40);
208
+ assert.equal(cmd[4], 0x00);
209
+ assert.equal(cmd[5], 0x03);
210
+ });
211
+ it("setFan1Speed validates range", () => {
212
+ assert.throws(() => writeCommands.setFan1Speed(-1), /must be 0-5/);
213
+ assert.throws(() => writeCommands.setFan1Speed(6), /must be 0-5/);
214
+ assert.doesNotThrow(() => writeCommands.setFan1Speed(0)); // auto
215
+ assert.doesNotThrow(() => writeCommands.setFan1Speed(5));
216
+ });
217
+ it("setFan2Speed validates range", () => {
218
+ assert.throws(() => writeCommands.setFan2Speed(-1), /must be 0-5/);
219
+ assert.throws(() => writeCommands.setFan2Speed(6), /must be 0-5/);
220
+ assert.doesNotThrow(() => writeCommands.setFan2Speed(0));
221
+ assert.doesNotThrow(() => writeCommands.setFan2Speed(5));
222
+ });
223
+ it("setAutoMode produces correct bytes", () => {
224
+ const cmdOn = writeCommands.setAutoMode(true);
225
+ assert.equal(cmdOn[5], 0x01);
226
+ const cmdOff = writeCommands.setAutoMode(false);
227
+ assert.equal(cmdOff[5], 0x00);
228
+ });
229
+ it("setStandby produces correct bytes", () => {
230
+ const cmdOn = writeCommands.setStandby(true);
231
+ assert.equal(cmdOn[5], 0x01);
232
+ const cmdOff = writeCommands.setStandby(false);
233
+ assert.equal(cmdOff[5], 0x00);
234
+ });
235
+ it("all write commands are 6 bytes", () => {
236
+ const commands = [
237
+ writeCommands.setPower(true),
238
+ writeCommands.setTemperature(21),
239
+ writeCommands.setPowerLevel(3),
240
+ writeCommands.setFan1Speed(2),
241
+ writeCommands.setFan2Speed(2),
242
+ writeCommands.setAutoMode(true),
243
+ writeCommands.setStandby(false),
244
+ ];
245
+ commands.forEach((cmd, i) => {
246
+ assert.equal(cmd.length, 6, `Command ${i} should be 6 bytes`);
247
+ });
248
+ });
249
+ it("all write commands use function code 0x06", () => {
250
+ const commands = [
251
+ writeCommands.setPower(true),
252
+ writeCommands.setTemperature(21),
253
+ writeCommands.setPowerLevel(3),
254
+ writeCommands.setFan1Speed(2),
255
+ writeCommands.setFan2Speed(2),
256
+ writeCommands.setAutoMode(true),
257
+ writeCommands.setStandby(false),
258
+ ];
259
+ commands.forEach((cmd, i) => {
260
+ assert.equal(cmd[1], 0x06, `Command ${i} should use function code 0x06`);
261
+ });
262
+ });
263
+ });
264
+ describe("parsers", () => {
265
+ it("boolean parser returns true for 0x01", () => {
266
+ const response = {
267
+ slaveAddress: 1,
268
+ functionCode: 0x03,
269
+ byteCount: 2,
270
+ data: new Uint8Array([0x00, 0x01]),
271
+ isError: false,
272
+ };
273
+ assert.equal(parsers.boolean(response), true);
274
+ });
275
+ it("boolean parser returns false for 0x00", () => {
276
+ const response = {
277
+ slaveAddress: 1,
278
+ functionCode: 0x03,
279
+ byteCount: 2,
280
+ data: new Uint8Array([0x00, 0x00]),
281
+ isError: false,
282
+ };
283
+ assert.equal(parsers.boolean(response), false);
284
+ });
285
+ it("temperature parser divides by 10", () => {
286
+ const response = {
287
+ slaveAddress: 1,
288
+ functionCode: 0x03,
289
+ byteCount: 2,
290
+ data: new Uint8Array([0x00, 0xd7]), // 215
291
+ isError: false,
292
+ };
293
+ assert.equal(parsers.temperature(response), 21.5);
294
+ });
295
+ it("temperature parser handles high temperatures", () => {
296
+ const response = {
297
+ slaveAddress: 1,
298
+ functionCode: 0x03,
299
+ byteCount: 2,
300
+ data: new Uint8Array([0x01, 0x2c]), // 300
301
+ isError: false,
302
+ };
303
+ assert.equal(parsers.temperature(response), 30);
304
+ });
305
+ it("number parser returns big-endian value", () => {
306
+ const response = {
307
+ slaveAddress: 1,
308
+ functionCode: 0x03,
309
+ byteCount: 2,
310
+ data: new Uint8Array([0x00, 0x03]), // power level 3
311
+ isError: false,
312
+ };
313
+ assert.equal(parsers.number(response), 3);
314
+ });
315
+ it("number parser handles larger values", () => {
316
+ const response = {
317
+ slaveAddress: 1,
318
+ functionCode: 0x03,
319
+ byteCount: 2,
320
+ data: new Uint8Array([0x01, 0x00]), // 256
321
+ isError: false,
322
+ };
323
+ assert.equal(parsers.number(response), 256);
324
+ });
325
+ it("boolean parser throws on error response", () => {
326
+ const errorResponse = {
327
+ slaveAddress: 1,
328
+ functionCode: 0x03,
329
+ data: new Uint8Array([0x02]), // error code
330
+ isError: true,
331
+ };
332
+ assert.throws(() => parsers.boolean(errorResponse), /Modbus error: 2/);
333
+ });
334
+ it("temperature parser throws on error response", () => {
335
+ const errorResponse = {
336
+ slaveAddress: 1,
337
+ functionCode: 0x03,
338
+ data: new Uint8Array([0x03]),
339
+ isError: true,
340
+ };
341
+ assert.throws(() => parsers.temperature(errorResponse), /Modbus error: 3/);
342
+ });
343
+ it("number parser throws on error response", () => {
344
+ const errorResponse = {
345
+ slaveAddress: 1,
346
+ functionCode: 0x03,
347
+ data: new Uint8Array([0x04]),
348
+ isError: true,
349
+ };
350
+ assert.throws(() => parsers.number(errorResponse), /Modbus error: 4/);
351
+ });
352
+ });
353
+ describe("integration", () => {
354
+ it("full roundtrip: create packet, decrypt, check structure", () => __awaiter(void 0, void 0, void 0, function* () {
355
+ // Create a power-on command
356
+ const command = writeCommands.setPower(true);
357
+ assert.deepEqual(command, new Uint8Array([0x01, 0x06, 0x03, 0x1c, 0x00, 0x01]));
358
+ // Create encrypted packet
359
+ const packet = yield createPacket(command);
360
+ assert.equal(packet.length, 32);
361
+ // Decrypt it back (as if we received it)
362
+ // Note: createPacket encrypts with padding, parseResponse expects response format
363
+ // This is not a true response but tests the encryption layer
364
+ }));
365
+ it("all read commands can create valid packets", () => __awaiter(void 0, void 0, void 0, function* () {
366
+ for (const [name, command] of Object.entries(readCommands)) {
367
+ const packet = yield createPacket(command);
368
+ assert.equal(packet.length, 32, `${name} should create 32-byte packet`);
369
+ }
370
+ }));
371
+ it("all write commands can create valid packets", () => __awaiter(void 0, void 0, void 0, function* () {
372
+ const commands = [
373
+ { name: "setPower", cmd: writeCommands.setPower(true) },
374
+ { name: "setTemperature", cmd: writeCommands.setTemperature(21) },
375
+ { name: "setPowerLevel", cmd: writeCommands.setPowerLevel(3) },
376
+ { name: "setFan1Speed", cmd: writeCommands.setFan1Speed(2) },
377
+ { name: "setFan2Speed", cmd: writeCommands.setFan2Speed(2) },
378
+ { name: "setAutoMode", cmd: writeCommands.setAutoMode(true) },
379
+ { name: "setStandby", cmd: writeCommands.setStandby(false) },
380
+ ];
381
+ for (const { name, cmd } of commands) {
382
+ const packet = yield createPacket(cmd);
383
+ assert.equal(packet.length, 32, `${name} should create 32-byte packet`);
384
+ }
385
+ }));
386
+ });
387
+ });
@@ -1,3 +1,4 @@
1
+ import { normalizeMac } from "./mac-utils";
1
2
  /**
2
3
  * Converts a BLE MAC address to WiFi MAC address.
3
4
  * The WiFi MAC is the BLE MAC minus 2 in hexadecimal.
@@ -10,12 +11,7 @@
10
11
  * bleToWifiMac("a8032afed50a") // returns "a8032afed508"
11
12
  */
12
13
  const bleToWifiMac = (bleMac) => {
13
- // Remove colons, dashes, and convert to lowercase
14
- const normalized = bleMac.replace(/[:-]/g, "").toLowerCase();
15
- // Validate MAC address format (12 hex characters)
16
- if (!/^[0-9a-f]{12}$/.test(normalized)) {
17
- throw new Error(`Invalid MAC address format: ${bleMac}`);
18
- }
14
+ const normalized = normalizeMac(bleMac);
19
15
  // Convert to number, subtract 2, convert back to hex
20
16
  const bleValue = BigInt(`0x${normalized}`);
21
17
  const wifiValue = bleValue - BigInt(2);
@@ -38,3 +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";
@@ -98,3 +98,11 @@ const scanWithOptions = (options) => __awaiter(void 0, void 0, void 0, function*
98
98
  return navigator.bluetooth.requestDevice(options);
99
99
  });
100
100
  export { EDILKAMIN_DEVICE_NAME, EDILKAMIN_SERVICE_UUID, isWebBluetoothSupported, scanForDevices, scanWithOptions, };
101
+ // Protocol functions
102
+ export { aesDecrypt, aesEncrypt, crc16Modbus, createPacket,
103
+ // Constants
104
+ NOTIFY_CHARACTERISTIC_UUID, parseResponse,
105
+ // Commands
106
+ parsers, readCommands, SERVICE_UUID, WRITE_CHARACTERISTIC_UUID,
107
+ // Parsers
108
+ writeCommands, } from "./bluetooth-protocol";
@@ -13,6 +13,7 @@ import readline from "readline";
13
13
  import { version } from "../package.json";
14
14
  import { NEW_API_URL, OLD_API_URL } from "./constants";
15
15
  import { configure, configureAmplify, getSession, signIn } from "./library";
16
+ import { normalizeMac } from "./mac-utils";
16
17
  import { clearSession, createFileStorage } from "./token-storage";
17
18
  import { AlarmCode, AlarmDescriptions } from "./types";
18
19
  const promptPassword = () => {
@@ -66,7 +67,7 @@ const addLegacyOption = (command) => command.option("--legacy", "Use legacy API
66
67
  */
67
68
  const initializeCommand = (options) => __awaiter(void 0, void 0, void 0, function* () {
68
69
  const { username, password, mac, legacy = false } = options;
69
- const normalizedMac = mac.replace(/:/g, "");
70
+ const normalizedMac = normalizeMac(mac);
70
71
  // Initialize file storage for session persistence
71
72
  const storage = createFileStorage();
72
73
  configureAmplify(storage);
@@ -389,7 +390,7 @@ const createProgram = () => {
389
390
  .command("getFanSpeed")
390
391
  .description("Retrieve fan speed by index (1-3)")).requiredOption("-i, --index <number>", "Fan index (1, 2, or 3)", parseInt))).action((options) => __awaiter(void 0, void 0, void 0, function* () {
391
392
  const { username, password, mac, index, legacy = false } = options;
392
- const normalizedMac = mac.replace(/:/g, "");
393
+ const normalizedMac = normalizeMac(mac);
393
394
  const storage = createFileStorage();
394
395
  configureAmplify(storage);
395
396
  let jwtToken;
@@ -412,7 +413,7 @@ const createProgram = () => {
412
413
  .command("getTargetTemperature")
413
414
  .description("Retrieve target temperature by environment index (1-3)")).requiredOption("-i, --index <number>", "Environment index (1, 2, or 3)", parseInt))).action((options) => __awaiter(void 0, void 0, void 0, function* () {
414
415
  const { username, password, mac, index, legacy = false } = options;
415
- const normalizedMac = mac.replace(/:/g, "");
416
+ const normalizedMac = normalizeMac(mac);
416
417
  const storage = createFileStorage();
417
418
  configureAmplify(storage);
418
419
  let jwtToken;
@@ -438,7 +439,7 @@ const createProgram = () => {
438
439
  .requiredOption("-i, --index <number>", "Fan index (1, 2, or 3)", parseInt)
439
440
  .requiredOption("-v, --value <number>", "Fan speed (0-5)", parseFloat))).action((options) => __awaiter(void 0, void 0, void 0, function* () {
440
441
  const { username, password, mac, index, value, legacy = false } = options;
441
- const normalizedMac = mac.replace(/:/g, "");
442
+ const normalizedMac = normalizeMac(mac);
442
443
  const storage = createFileStorage();
443
444
  configureAmplify(storage);
444
445
  let jwtToken;
@@ -463,7 +464,7 @@ const createProgram = () => {
463
464
  .requiredOption("-i, --index <number>", "Environment index (1, 2, or 3)", parseInt)
464
465
  .requiredOption("-v, --value <number>", "Temperature in degrees Celsius", parseFloat))).action((options) => __awaiter(void 0, void 0, void 0, function* () {
465
466
  const { username, password, mac, index, value, legacy = false } = options;
466
- const normalizedMac = mac.replace(/:/g, "");
467
+ const normalizedMac = normalizeMac(mac);
467
468
  const storage = createFileStorage();
468
469
  configureAmplify(storage);
469
470
  let jwtToken;
@@ -487,7 +488,7 @@ const createProgram = () => {
487
488
  .command("getAlarmHistory")
488
489
  .description("Get alarm history log with human-readable descriptions")))).action((options) => __awaiter(void 0, void 0, void 0, function* () {
489
490
  const { username, password, mac, legacy = false } = options;
490
- const normalizedMac = mac.replace(/:/g, "");
491
+ const normalizedMac = normalizeMac(mac);
491
492
  const storage = createFileStorage();
492
493
  configureAmplify(storage);
493
494
  let jwtToken;
@@ -518,7 +519,7 @@ const createProgram = () => {
518
519
  .requiredOption("-r, --room <deviceRoom>", "Room name")
519
520
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
520
521
  const { username, password, mac, serial, name, room, legacy = false, } = options;
521
- const normalizedMac = mac.replace(/:/g, "");
522
+ const normalizedMac = normalizeMac(mac);
522
523
  // Initialize file storage for session persistence
523
524
  const storage = createFileStorage();
524
525
  configureAmplify(storage);
@@ -547,7 +548,7 @@ const createProgram = () => {
547
548
  .requiredOption("-r, --room <deviceRoom>", "Room name")
548
549
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
549
550
  const { username, password, mac, name, room, legacy = false } = options;
550
- const normalizedMac = mac.replace(/:/g, "");
551
+ const normalizedMac = normalizeMac(mac);
551
552
  // Initialize file storage for session persistence
552
553
  const storage = createFileStorage();
553
554
  configureAmplify(storage);
@@ -1,8 +1,9 @@
1
1
  export { bleToWifiMac } from "./bluetooth-utils";
2
2
  export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
3
3
  export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
4
- export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
4
+ export { configure, derivePhaseDescription, deriveUsageAnalytics, getPhaseDescription, getSession, signIn, } from "./library";
5
+ export { normalizeMac } from "./mac-utils";
5
6
  export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
6
- export { AlarmEntryType, AlarmsLogType, BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, DiscoveredDevice, EditDeviceAssociationBody, PowerDistributionType, RegenerationDataType, ServiceCountersType, ServiceStatusType, StatusCountersType, StatusType, TemperaturesType, TotalCountersType, UsageAnalyticsType, UserParametersType, } from "./types";
7
- export { AlarmCode, AlarmDescriptions } from "./types";
7
+ export { AlarmEntryType, AlarmsLogType, BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, DiscoveredDevice, EditDeviceAssociationBody, FansType, PowerDistributionType, RegenerationDataType, ServiceCountersType, ServiceStatusType, StateType, StatusCountersType, StatusType, TemperaturesType, TotalCountersType, UsageAnalyticsType, UserParametersType, } from "./types";
8
+ export { AlarmCode, AlarmDescriptions, getIgnitionSubPhaseDescription, getOperationalPhaseDescription, getStoveStateDescription, IgnitionSubPhase, IgnitionSubPhaseDescriptions, OperationalPhase, OperationalPhaseDescriptions, StoveState, StoveStateDescriptions, } from "./types";
8
9
  export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("./types").DeviceInfoType>, registerDevice: (jwtToken: string, macAddress: string, serialNumber: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, editDevice: (jwtToken: string, macAddress: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, setPower: (jwtToken: string, macAddress: string, value: number) => Promise<unknown>, setPowerOff: (jwtToken: string, macAddress: string) => Promise<unknown>, setPowerOn: (jwtToken: string, macAddress: string) => Promise<unknown>, getPower: (jwtToken: string, macAddress: string) => Promise<boolean>, getEnvironmentTemperature: (jwtToken: string, macAddress: string) => Promise<number>, getTargetTemperature: (jwtToken: string, macAddress: string, envIndex: 1 | 2 | 3) => Promise<number>, setTargetTemperature: (jwtToken: string, macAddress: string, envIndex: 1 | 2 | 3, temperature: number) => Promise<unknown>;
@@ -2,7 +2,8 @@ import { configure } from "./library";
2
2
  export { bleToWifiMac } from "./bluetooth-utils";
3
3
  export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
4
4
  export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
5
- export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
5
+ export { configure, derivePhaseDescription, deriveUsageAnalytics, getPhaseDescription, getSession, signIn, } from "./library";
6
+ export { normalizeMac } from "./mac-utils";
6
7
  export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
7
- export { AlarmCode, AlarmDescriptions } from "./types";
8
+ export { AlarmCode, AlarmDescriptions, getIgnitionSubPhaseDescription, getOperationalPhaseDescription, getStoveStateDescription, IgnitionSubPhase, IgnitionSubPhaseDescriptions, OperationalPhase, OperationalPhaseDescriptions, StoveState, StoveStateDescriptions, } from "./types";
8
9
  export const { deviceInfo, registerDevice, editDevice, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
@@ -45,6 +45,32 @@ declare const signIn: (username: string, password: string, legacy?: boolean) =>
45
45
  * const analytics = deriveUsageAnalytics(info);
46
46
  */
47
47
  export declare const deriveUsageAnalytics: (deviceInfo: DeviceInfoType, serviceThreshold?: number) => UsageAnalyticsType;
48
+ /**
49
+ * Get human-readable description of the current device phase.
50
+ * Combines operational_phase and sub_operational_phase for context.
51
+ *
52
+ * @param {number} operationalPhase - The main operational phase.
53
+ * @param {number} subOperationalPhase - The sub-phase (used during ignition).
54
+ * @returns {string} - Human-readable phase description.
55
+ *
56
+ * @example
57
+ * const desc = getPhaseDescription(2, 1);
58
+ * // Returns: "Ignition - Pellet load"
59
+ */
60
+ export declare const getPhaseDescription: (operationalPhase: number, subOperationalPhase: number) => string;
61
+ /**
62
+ * Derive phase description from existing DeviceInfo.
63
+ * Pure function - no API calls required.
64
+ *
65
+ * @param {DeviceInfoType} deviceInfo - The device info object.
66
+ * @returns {string} - Human-readable phase description.
67
+ *
68
+ * @example
69
+ * const info = await api.deviceInfo(token, mac);
70
+ * const desc = derivePhaseDescription(info);
71
+ * // Returns: "On" or "Ignition - Warmup" etc.
72
+ */
73
+ export declare const derivePhaseDescription: (deviceInfo: DeviceInfoType) => string;
48
74
  /**
49
75
  * Configures the library for API interactions.
50
76
  * Initializes API methods with a specified base URL.
@@ -97,6 +123,10 @@ declare const configure: (baseURL?: string) => {
97
123
  getLanguage: (jwtToken: string, macAddress: string) => Promise<number>;
98
124
  getPelletInReserve: (jwtToken: string, macAddress: string) => Promise<boolean>;
99
125
  getPelletAutonomyTime: (jwtToken: string, macAddress: string) => Promise<number>;
126
+ getOperationalPhase: (jwtToken: string, macAddress: string) => Promise<number>;
127
+ getSubOperationalPhase: (jwtToken: string, macAddress: string) => Promise<number>;
128
+ getStoveState: (jwtToken: string, macAddress: string) => Promise<number>;
129
+ getActualPower: (jwtToken: string, macAddress: string) => Promise<number>;
100
130
  getTotalCounters: (jwtToken: string, macAddress: string) => Promise<TotalCountersType>;
101
131
  getServiceCounters: (jwtToken: string, macAddress: string) => Promise<ServiceCountersType>;
102
132
  getAlarmHistory: (jwtToken: string, macAddress: string) => Promise<AlarmsLogType>;