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
@@ -42,13 +42,15 @@ 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.deriveUsageAnalytics = void 0;
45
+ exports.signIn = exports.headers = exports.getSession = exports.createAuthService = exports.configureAmplify = exports.configure = exports.derivePhaseDescription = exports.getPhaseDescription = exports.deriveUsageAnalytics = 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"));
49
49
  const cognito_1 = require("aws-amplify/auth/cognito");
50
50
  const buffer_utils_1 = require("./buffer-utils");
51
51
  const constants_1 = require("./constants");
52
+ const mac_utils_1 = require("./mac-utils");
53
+ const types_1 = require("./types");
52
54
  /**
53
55
  * Makes a fetch request and returns parsed JSON response.
54
56
  * Throws an error for non-2xx status codes.
@@ -514,6 +516,56 @@ const getPelletAutonomyTime = (baseURL) =>
514
516
  const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
515
517
  return info.status.pellet.autonomy_time;
516
518
  });
519
+ const getOperationalPhase = (baseURL) =>
520
+ /**
521
+ * Retrieves the current operational phase of the stove.
522
+ *
523
+ * @param {string} jwtToken - The JWT token for authentication.
524
+ * @param {string} macAddress - The MAC address of the device.
525
+ * @returns {Promise<number>} - The operational phase (0=Off, 1=Standby, 2=Ignition, 6=On).
526
+ */
527
+ (jwtToken, macAddress) => __awaiter(void 0, void 0, void 0, function* () {
528
+ const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
529
+ return info.status.state.operational_phase;
530
+ });
531
+ const getSubOperationalPhase = (baseURL) =>
532
+ /**
533
+ * Retrieves the current sub-operational phase of the stove.
534
+ * Only meaningful during ignition (operational_phase === 2).
535
+ *
536
+ * @param {string} jwtToken - The JWT token for authentication.
537
+ * @param {string} macAddress - The MAC address of the device.
538
+ * @returns {Promise<number>} - The sub-operational phase (0-6 during ignition).
539
+ */
540
+ (jwtToken, macAddress) => __awaiter(void 0, void 0, void 0, function* () {
541
+ const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
542
+ return info.status.state.sub_operational_phase;
543
+ });
544
+ const getStoveState = (baseURL) =>
545
+ /**
546
+ * Retrieves the combined stove state code.
547
+ *
548
+ * @param {string} jwtToken - The JWT token for authentication.
549
+ * @param {string} macAddress - The MAC address of the device.
550
+ * @returns {Promise<number>} - The stove state code.
551
+ */
552
+ (jwtToken, macAddress) => __awaiter(void 0, void 0, void 0, function* () {
553
+ const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
554
+ return info.status.state.stove_state;
555
+ });
556
+ const getActualPower = (baseURL) =>
557
+ /**
558
+ * Retrieves the actual power level the stove is currently running at.
559
+ * This may differ from the requested power level during transitions.
560
+ *
561
+ * @param {string} jwtToken - The JWT token for authentication.
562
+ * @param {string} macAddress - The MAC address of the device.
563
+ * @returns {Promise<number>} - The actual power level (1-5).
564
+ */
565
+ (jwtToken, macAddress) => __awaiter(void 0, void 0, void 0, function* () {
566
+ const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
567
+ return info.status.state.actual_power;
568
+ });
517
569
  const getTotalCounters = (baseURL) =>
518
570
  /**
519
571
  * Retrieves lifetime operating counters.
@@ -735,7 +787,7 @@ const registerDevice = (baseURL) =>
735
787
  */
736
788
  (jwtToken_1, macAddress_1, serialNumber_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, serialNumber_1, ...args_1], void 0, function* (jwtToken, macAddress, serialNumber, deviceName = "", deviceRoom = "") {
737
789
  const body = {
738
- macAddress: macAddress.replace(/:/g, ""),
790
+ macAddress: (0, mac_utils_1.normalizeMac)(macAddress),
739
791
  deviceName,
740
792
  deviceRoom,
741
793
  serialNumber,
@@ -757,7 +809,7 @@ const editDevice = (baseURL) =>
757
809
  * @returns {Promise<DeviceAssociationResponse>} - A promise that resolves to the update response.
758
810
  */
759
811
  (jwtToken_1, macAddress_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, ...args_1], void 0, function* (jwtToken, macAddress, deviceName = "", deviceRoom = "") {
760
- const normalizedMac = macAddress.replace(/:/g, "");
812
+ const normalizedMac = (0, mac_utils_1.normalizeMac)(macAddress);
761
813
  const body = {
762
814
  deviceName,
763
815
  deviceRoom,
@@ -768,6 +820,43 @@ const editDevice = (baseURL) =>
768
820
  body: JSON.stringify(body),
769
821
  });
770
822
  });
823
+ /**
824
+ * Get human-readable description of the current device phase.
825
+ * Combines operational_phase and sub_operational_phase for context.
826
+ *
827
+ * @param {number} operationalPhase - The main operational phase.
828
+ * @param {number} subOperationalPhase - The sub-phase (used during ignition).
829
+ * @returns {string} - Human-readable phase description.
830
+ *
831
+ * @example
832
+ * const desc = getPhaseDescription(2, 1);
833
+ * // Returns: "Ignition - Pellet load"
834
+ */
835
+ const getPhaseDescription = (operationalPhase, subOperationalPhase) => {
836
+ if (operationalPhase === types_1.OperationalPhase.IGNITION) {
837
+ const subDesc = (0, types_1.getIgnitionSubPhaseDescription)(subOperationalPhase);
838
+ return `Ignition - ${subDesc}`;
839
+ }
840
+ return (0, types_1.getOperationalPhaseDescription)(operationalPhase);
841
+ };
842
+ exports.getPhaseDescription = getPhaseDescription;
843
+ /**
844
+ * Derive phase description from existing DeviceInfo.
845
+ * Pure function - no API calls required.
846
+ *
847
+ * @param {DeviceInfoType} deviceInfo - The device info object.
848
+ * @returns {string} - Human-readable phase description.
849
+ *
850
+ * @example
851
+ * const info = await api.deviceInfo(token, mac);
852
+ * const desc = derivePhaseDescription(info);
853
+ * // Returns: "On" or "Ignition - Warmup" etc.
854
+ */
855
+ const derivePhaseDescription = (deviceInfo) => {
856
+ const { operational_phase, sub_operational_phase } = deviceInfo.status.state;
857
+ return (0, exports.getPhaseDescription)(operational_phase, sub_operational_phase);
858
+ };
859
+ exports.derivePhaseDescription = derivePhaseDescription;
771
860
  /**
772
861
  * Configures the library for API interactions.
773
862
  * Initializes API methods with a specified base URL.
@@ -820,6 +909,11 @@ const configure = (baseURL = constants_1.API_URL) => ({
820
909
  getLanguage: getLanguage(baseURL),
821
910
  getPelletInReserve: getPelletInReserve(baseURL),
822
911
  getPelletAutonomyTime: getPelletAutonomyTime(baseURL),
912
+ // Phase/state getters
913
+ getOperationalPhase: getOperationalPhase(baseURL),
914
+ getSubOperationalPhase: getSubOperationalPhase(baseURL),
915
+ getStoveState: getStoveState(baseURL),
916
+ getActualPower: getActualPower(baseURL),
823
917
  // Statistics getters
824
918
  getTotalCounters: getTotalCounters(baseURL),
825
919
  getServiceCounters: getServiceCounters(baseURL),
@@ -16,6 +16,7 @@ const assert_1 = require("assert");
16
16
  const pako_1 = __importDefault(require("pako"));
17
17
  const sinon_1 = __importDefault(require("sinon"));
18
18
  const library_1 = require("../src/library");
19
+ const types_1 = require("../src/types");
19
20
  const constants_1 = require("./constants");
20
21
  /**
21
22
  * Helper to create a gzip-compressed Buffer object for testing.
@@ -220,6 +221,11 @@ describe("library", () => {
220
221
  "getLanguage",
221
222
  "getPelletInReserve",
222
223
  "getPelletAutonomyTime",
224
+ // Phase/state getters
225
+ "getOperationalPhase",
226
+ "getSubOperationalPhase",
227
+ "getStoveState",
228
+ "getActualPower",
223
229
  // Statistics getters
224
230
  "getTotalCounters",
225
231
  "getServiceCounters",
@@ -603,7 +609,7 @@ describe("library", () => {
603
609
  Authorization: `Bearer ${expectedToken}`,
604
610
  },
605
611
  body: JSON.stringify({
606
- macAddress: "AABBCCDDEEFF",
612
+ macAddress: "aabbccddeeff",
607
613
  deviceName: "Test Stove",
608
614
  deviceRoom: "Living Room",
609
615
  serialNumber: "EDK123",
@@ -611,12 +617,12 @@ describe("library", () => {
611
617
  });
612
618
  assert_1.strict.deepEqual(result, mockResponseData);
613
619
  }));
614
- it("should normalize MAC address by removing colons", () => __awaiter(void 0, void 0, void 0, function* () {
620
+ it("should normalize MAC address by removing colons and converting to lowercase", () => __awaiter(void 0, void 0, void 0, function* () {
615
621
  fetchStub.resolves(mockResponse({}));
616
622
  const api = (0, library_1.configure)("https://example.com/api/");
617
623
  yield api.registerDevice(expectedToken, "AA:BB:CC:DD:EE:FF", "EDK123");
618
624
  const body = JSON.parse(fetchStub.firstCall.args[1].body);
619
- assert_1.strict.equal(body.macAddress, "AABBCCDDEEFF");
625
+ assert_1.strict.equal(body.macAddress, "aabbccddeeff");
620
626
  }));
621
627
  it("should use empty strings as defaults for name and room", () => __awaiter(void 0, void 0, void 0, function* () {
622
628
  fetchStub.resolves(mockResponse({}));
@@ -639,7 +645,7 @@ describe("library", () => {
639
645
  const api = (0, library_1.configure)("https://example.com/api/");
640
646
  const result = yield api.editDevice(expectedToken, "AA:BB:CC:DD:EE:FF", "Updated Name", "Basement");
641
647
  assert_1.strict.ok(fetchStub.calledOnce);
642
- assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device/AABBCCDDEEFF");
648
+ assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device/aabbccddeeff");
643
649
  assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
644
650
  method: "PUT",
645
651
  headers: {
@@ -1147,6 +1153,221 @@ describe("library", () => {
1147
1153
  assert_1.strict.equal(analytics.lastMaintenanceDate, null);
1148
1154
  });
1149
1155
  });
1156
+ describe("phase getters", () => {
1157
+ const mockDeviceInfoWithState = {
1158
+ status: {
1159
+ commands: { power: true },
1160
+ temperatures: { board: 25, enviroment: 20 },
1161
+ flags: { is_pellet_in_reserve: false },
1162
+ pellet: { autonomy_time: 900 },
1163
+ counters: { service_time: 1108 },
1164
+ state: {
1165
+ operational_phase: 2,
1166
+ sub_operational_phase: 3,
1167
+ stove_state: 4,
1168
+ alarm_type: 0,
1169
+ actual_power: 3,
1170
+ },
1171
+ fans: { fan_1_speed: 3, fan_2_speed: 0, fan_3_speed: 0 },
1172
+ },
1173
+ nvm: {
1174
+ user_parameters: {
1175
+ language: 1,
1176
+ is_auto: false,
1177
+ is_fahrenheit: false,
1178
+ is_sound_active: false,
1179
+ enviroment_1_temperature: 19,
1180
+ enviroment_2_temperature: 20,
1181
+ enviroment_3_temperature: 20,
1182
+ manual_power: 1,
1183
+ fan_1_ventilation: 3,
1184
+ fan_2_ventilation: 0,
1185
+ fan_3_ventilation: 0,
1186
+ is_standby_active: false,
1187
+ standby_waiting_time: 60,
1188
+ },
1189
+ total_counters: {
1190
+ power_ons: 278,
1191
+ p1_working_time: 833,
1192
+ p2_working_time: 15,
1193
+ p3_working_time: 19,
1194
+ p4_working_time: 8,
1195
+ p5_working_time: 17,
1196
+ },
1197
+ service_counters: {
1198
+ p1_working_time: 100,
1199
+ p2_working_time: 10,
1200
+ p3_working_time: 5,
1201
+ p4_working_time: 2,
1202
+ p5_working_time: 1,
1203
+ },
1204
+ alarms_log: {
1205
+ number: 2,
1206
+ index: 2,
1207
+ alarms: [],
1208
+ },
1209
+ regeneration: {
1210
+ time: 0,
1211
+ last_intervention: 1577836800,
1212
+ daylight_time_flag: 0,
1213
+ blackout_counter: 43,
1214
+ airkare_working_hours_counter: 0,
1215
+ },
1216
+ },
1217
+ };
1218
+ it("should get operational phase", () => __awaiter(void 0, void 0, void 0, function* () {
1219
+ fetchStub.resolves(mockResponse(mockDeviceInfoWithState));
1220
+ const api = (0, library_1.configure)(constants_1.API_URL);
1221
+ const result = yield api.getOperationalPhase(expectedToken, "00:11:22:33:44:55");
1222
+ assert_1.strict.equal(result, 2);
1223
+ }));
1224
+ it("should get sub-operational phase", () => __awaiter(void 0, void 0, void 0, function* () {
1225
+ fetchStub.resolves(mockResponse(mockDeviceInfoWithState));
1226
+ const api = (0, library_1.configure)(constants_1.API_URL);
1227
+ const result = yield api.getSubOperationalPhase(expectedToken, "00:11:22:33:44:55");
1228
+ assert_1.strict.equal(result, 3);
1229
+ }));
1230
+ it("should get stove state", () => __awaiter(void 0, void 0, void 0, function* () {
1231
+ fetchStub.resolves(mockResponse(mockDeviceInfoWithState));
1232
+ const api = (0, library_1.configure)(constants_1.API_URL);
1233
+ const result = yield api.getStoveState(expectedToken, "00:11:22:33:44:55");
1234
+ assert_1.strict.equal(result, 4);
1235
+ }));
1236
+ it("should get actual power", () => __awaiter(void 0, void 0, void 0, function* () {
1237
+ fetchStub.resolves(mockResponse(mockDeviceInfoWithState));
1238
+ const api = (0, library_1.configure)(constants_1.API_URL);
1239
+ const result = yield api.getActualPower(expectedToken, "00:11:22:33:44:55");
1240
+ assert_1.strict.equal(result, 3);
1241
+ }));
1242
+ });
1243
+ describe("getPhaseDescription", () => {
1244
+ it("should return 'Off' for phase 0", () => {
1245
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(0, 0), "Off");
1246
+ });
1247
+ it("should return 'Standby' for phase 1", () => {
1248
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(1, 0), "Standby");
1249
+ });
1250
+ it("should return 'On' for phase 6", () => {
1251
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(6, 0), "On");
1252
+ });
1253
+ it("should return combined description for ignition phase", () => {
1254
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 0), "Ignition - Starting cleaning");
1255
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 1), "Ignition - Pellet load");
1256
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 2), "Ignition - Loading break");
1257
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 3), "Ignition - Smoke temperature check");
1258
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 4), "Ignition - Threshold exceeding check");
1259
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 5), "Ignition - Warmup");
1260
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 6), "Ignition - Starting up");
1261
+ });
1262
+ it("should return fallback for unknown operational phase", () => {
1263
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(99, 0), "Unknown phase (99)");
1264
+ });
1265
+ it("should return fallback for unknown ignition sub-phase", () => {
1266
+ assert_1.strict.equal((0, library_1.getPhaseDescription)(2, 99), "Ignition - Unknown sub-phase (99)");
1267
+ });
1268
+ });
1269
+ describe("derivePhaseDescription", () => {
1270
+ it("should derive phase description from device info", () => {
1271
+ const mockDeviceInfo = {
1272
+ status: {
1273
+ commands: { power: true },
1274
+ temperatures: { board: 25, enviroment: 22 },
1275
+ flags: { is_pellet_in_reserve: true },
1276
+ pellet: { autonomy_time: 120 },
1277
+ counters: { service_time: 100 },
1278
+ state: {
1279
+ operational_phase: 2,
1280
+ sub_operational_phase: 5,
1281
+ stove_state: 5,
1282
+ alarm_type: 0,
1283
+ actual_power: 3,
1284
+ },
1285
+ fans: { fan_1_speed: 3, fan_2_speed: 0, fan_3_speed: 0 },
1286
+ },
1287
+ nvm: {
1288
+ user_parameters: {},
1289
+ total_counters: {},
1290
+ service_counters: {},
1291
+ alarms_log: { number: 0, index: 0, alarms: [] },
1292
+ regeneration: {},
1293
+ },
1294
+ };
1295
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1296
+ const desc = (0, library_1.derivePhaseDescription)(mockDeviceInfo);
1297
+ assert_1.strict.equal(desc, "Ignition - Warmup");
1298
+ });
1299
+ it("should return 'On' for device in On state", () => {
1300
+ const mockDeviceInfo = {
1301
+ status: {
1302
+ commands: { power: true },
1303
+ temperatures: { board: 25, enviroment: 22 },
1304
+ flags: { is_pellet_in_reserve: false },
1305
+ pellet: { autonomy_time: 120 },
1306
+ counters: { service_time: 100 },
1307
+ state: {
1308
+ operational_phase: 6,
1309
+ sub_operational_phase: 0,
1310
+ stove_state: 6,
1311
+ alarm_type: 0,
1312
+ actual_power: 3,
1313
+ },
1314
+ fans: { fan_1_speed: 3, fan_2_speed: 0, fan_3_speed: 0 },
1315
+ },
1316
+ nvm: {
1317
+ user_parameters: {},
1318
+ total_counters: {},
1319
+ service_counters: {},
1320
+ alarms_log: { number: 0, index: 0, alarms: [] },
1321
+ regeneration: {},
1322
+ },
1323
+ };
1324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1325
+ const desc = (0, library_1.derivePhaseDescription)(mockDeviceInfo);
1326
+ assert_1.strict.equal(desc, "On");
1327
+ });
1328
+ });
1329
+ describe("OperationalPhase descriptions", () => {
1330
+ it("should return descriptions for known phases", () => {
1331
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(types_1.OperationalPhase.OFF), "Off");
1332
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(types_1.OperationalPhase.STANDBY), "Standby");
1333
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(types_1.OperationalPhase.IGNITION), "Ignition");
1334
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(types_1.OperationalPhase.ON), "On");
1335
+ });
1336
+ it("should return fallback for unknown phases", () => {
1337
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(3), "Unknown phase (3)");
1338
+ assert_1.strict.equal((0, types_1.getOperationalPhaseDescription)(99), "Unknown phase (99)");
1339
+ });
1340
+ });
1341
+ describe("IgnitionSubPhase descriptions", () => {
1342
+ it("should return descriptions for all ignition sub-phases", () => {
1343
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.STARTING_CLEANING), "Starting cleaning");
1344
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.PELLET_LOAD), "Pellet load");
1345
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.LOADING_BREAK), "Loading break");
1346
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.SMOKE_TEMPERATURE_CHECK), "Smoke temperature check");
1347
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.THRESHOLD_EXCEEDING_CHECK), "Threshold exceeding check");
1348
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.WARMUP), "Warmup");
1349
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(types_1.IgnitionSubPhase.TRANSITION_TO_ON), "Starting up");
1350
+ });
1351
+ it("should return fallback for unknown sub-phases", () => {
1352
+ assert_1.strict.equal((0, types_1.getIgnitionSubPhaseDescription)(99), "Unknown sub-phase (99)");
1353
+ });
1354
+ });
1355
+ describe("StoveState descriptions", () => {
1356
+ it("should return descriptions for known states", () => {
1357
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.OFF), "Off");
1358
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.STANDBY), "Standby");
1359
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.IGNITION_CLEANING), "Ignition - Cleaning");
1360
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.IGNITION_LOADING), "Ignition - Loading pellets");
1361
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.IGNITION_WAITING), "Ignition - Waiting");
1362
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.IGNITION_WARMUP), "Ignition - Warming up");
1363
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.ON), "On");
1364
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.COOLING), "Cooling down");
1365
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(types_1.StoveState.ALARM), "Alarm");
1366
+ });
1367
+ it("should return fallback for unknown states", () => {
1368
+ assert_1.strict.equal((0, types_1.getStoveStateDescription)(99), "Unknown state (99)");
1369
+ });
1370
+ });
1150
1371
  describe("Error Handling", () => {
1151
1372
  const errorTests = [
1152
1373
  { status: 400, statusText: "Bad Request" },
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Normalizes a MAC address by removing separators and converting to lowercase.
3
+ * Accepts formats: AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF
4
+ *
5
+ * @param mac - MAC address in any common format
6
+ * @returns Normalized MAC address (12 lowercase hex chars, no separators)
7
+ * @throws Error if MAC address format is invalid
8
+ *
9
+ * @example
10
+ * normalizeMac("AA:BB:CC:DD:EE:FF") // returns "aabbccddeeff"
11
+ * normalizeMac("AA-BB-CC-DD-EE-FF") // returns "aabbccddeeff"
12
+ * normalizeMac("AABBCCDDEEFF") // returns "aabbccddeeff"
13
+ */
14
+ declare const normalizeMac: (mac: string) => string;
15
+ export { normalizeMac };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeMac = void 0;
4
+ /**
5
+ * Normalizes a MAC address by removing separators and converting to lowercase.
6
+ * Accepts formats: AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF
7
+ *
8
+ * @param mac - MAC address in any common format
9
+ * @returns Normalized MAC address (12 lowercase hex chars, no separators)
10
+ * @throws Error if MAC address format is invalid
11
+ *
12
+ * @example
13
+ * normalizeMac("AA:BB:CC:DD:EE:FF") // returns "aabbccddeeff"
14
+ * normalizeMac("AA-BB-CC-DD-EE-FF") // returns "aabbccddeeff"
15
+ * normalizeMac("AABBCCDDEEFF") // returns "aabbccddeeff"
16
+ */
17
+ const normalizeMac = (mac) => {
18
+ const normalized = mac.replace(/[:-]/g, "").toLowerCase();
19
+ if (!/^[0-9a-f]{12}$/.test(normalized)) {
20
+ throw new Error(`Invalid MAC address format: ${mac}`);
21
+ }
22
+ return normalized;
23
+ };
24
+ exports.normalizeMac = normalizeMac;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const assert_1 = require("assert");
4
+ const mac_utils_1 = require("./mac-utils");
5
+ describe("mac-utils", () => {
6
+ describe("normalizeMac", () => {
7
+ it("should normalize MAC address with colons", () => {
8
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("AA:BB:CC:DD:EE:FF"), "aabbccddeeff");
9
+ });
10
+ it("should normalize MAC address with dashes", () => {
11
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("AA-BB-CC-DD-EE-FF"), "aabbccddeeff");
12
+ });
13
+ it("should normalize MAC address without separators", () => {
14
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("AABBCCDDEEFF"), "aabbccddeeff");
15
+ });
16
+ it("should normalize lowercase MAC address", () => {
17
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("aa:bb:cc:dd:ee:ff"), "aabbccddeeff");
18
+ });
19
+ it("should normalize mixed case MAC address", () => {
20
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("Aa:Bb:Cc:Dd:Ee:Ff"), "aabbccddeeff");
21
+ });
22
+ it("should normalize MAC address with mixed separators", () => {
23
+ assert_1.strict.equal((0, mac_utils_1.normalizeMac)("AA:BB-CC:DD-EE:FF"), "aabbccddeeff");
24
+ });
25
+ it("should throw on MAC address with invalid length (too short)", () => {
26
+ assert_1.strict.throws(() => (0, mac_utils_1.normalizeMac)("AA:BB:CC:DD:EE"), /Invalid MAC address format: AA:BB:CC:DD:EE/);
27
+ });
28
+ it("should throw on MAC address with invalid length (too long)", () => {
29
+ assert_1.strict.throws(() => (0, mac_utils_1.normalizeMac)("AA:BB:CC:DD:EE:FF:00"), /Invalid MAC address format: AA:BB:CC:DD:EE:FF:00/);
30
+ });
31
+ it("should throw on MAC address with invalid characters", () => {
32
+ assert_1.strict.throws(() => (0, mac_utils_1.normalizeMac)("GG:HH:II:JJ:KK:LL"), /Invalid MAC address format: GG:HH:II:JJ:KK:LL/);
33
+ });
34
+ it("should throw on empty string", () => {
35
+ assert_1.strict.throws(() => (0, mac_utils_1.normalizeMac)(""), /Invalid MAC address format: /);
36
+ });
37
+ it("should throw on whitespace-only string", () => {
38
+ assert_1.strict.throws(() => (0, mac_utils_1.normalizeMac)(" "), /Invalid MAC address format:/);
39
+ });
40
+ });
41
+ });
@@ -25,12 +25,42 @@ interface PelletAutonomyType {
25
25
  interface StatusCountersType {
26
26
  service_time: number;
27
27
  }
28
+ /**
29
+ * Device operational state information.
30
+ * Retrieved from status.state in the API response.
31
+ */
32
+ interface StateType {
33
+ /** Main operational phase (0=Off, 1=Standby, 2=Ignition, 6=On) */
34
+ operational_phase: number;
35
+ /** Sub-phase within current operation (0-6 during ignition) */
36
+ sub_operational_phase: number;
37
+ /** Combined stove state code */
38
+ stove_state: number;
39
+ /** Current alarm code (0 = no alarm) */
40
+ alarm_type: number;
41
+ /** Current actual power level (1-5) */
42
+ actual_power: number;
43
+ }
44
+ /**
45
+ * Fan speed information for all three fans.
46
+ * Retrieved from status.fans in the API response.
47
+ */
48
+ interface FansType {
49
+ /** Fan 1 speed (0-5) */
50
+ fan_1_speed: number;
51
+ /** Fan 2 speed (0-5) */
52
+ fan_2_speed: number;
53
+ /** Fan 3 speed (0-5) */
54
+ fan_3_speed: number;
55
+ }
28
56
  interface StatusType {
29
57
  commands: CommandsType;
30
58
  temperatures: TemperaturesType;
31
59
  flags: GeneralFlagsType;
32
60
  pellet: PelletAutonomyType;
33
61
  counters: StatusCountersType;
62
+ state: StateType;
63
+ fans: FansType;
34
64
  }
35
65
  interface UserParametersType {
36
66
  enviroment_1_temperature: number;
@@ -161,6 +191,68 @@ declare enum AlarmCode {
161
191
  * Human-readable descriptions for alarm codes.
162
192
  */
163
193
  declare const AlarmDescriptions: Record<AlarmCode, string>;
194
+ /**
195
+ * Main operational phases of the stove.
196
+ * Values derived from device behavior observation.
197
+ */
198
+ declare enum OperationalPhase {
199
+ OFF = 0,
200
+ STANDBY = 1,
201
+ IGNITION = 2,
202
+ ON = 6
203
+ }
204
+ /**
205
+ * Human-readable descriptions for operational phases.
206
+ */
207
+ declare const OperationalPhaseDescriptions: Record<number, string>;
208
+ /**
209
+ * Get description for an operational phase, with fallback for unknown values.
210
+ */
211
+ declare const getOperationalPhaseDescription: (phase: number) => string;
212
+ /**
213
+ * Sub-phases during ignition sequence.
214
+ * These are only meaningful when operational_phase === IGNITION.
215
+ */
216
+ declare enum IgnitionSubPhase {
217
+ STARTING_CLEANING = 0,
218
+ PELLET_LOAD = 1,
219
+ LOADING_BREAK = 2,
220
+ SMOKE_TEMPERATURE_CHECK = 3,
221
+ THRESHOLD_EXCEEDING_CHECK = 4,
222
+ WARMUP = 5,
223
+ TRANSITION_TO_ON = 6
224
+ }
225
+ /**
226
+ * Human-readable descriptions for ignition sub-phases.
227
+ */
228
+ declare const IgnitionSubPhaseDescriptions: Record<number, string>;
229
+ /**
230
+ * Get description for an ignition sub-phase, with fallback for unknown values.
231
+ */
232
+ declare const getIgnitionSubPhaseDescription: (subPhase: number) => string;
233
+ /**
234
+ * Combined stove states.
235
+ * This is a composite value combining operational phase and sub-phase.
236
+ */
237
+ declare enum StoveState {
238
+ OFF = 0,
239
+ STANDBY = 1,
240
+ IGNITION_CLEANING = 2,
241
+ IGNITION_LOADING = 3,
242
+ IGNITION_WAITING = 4,
243
+ IGNITION_WARMUP = 5,
244
+ ON = 6,
245
+ COOLING = 7,
246
+ ALARM = 8
247
+ }
248
+ /**
249
+ * Human-readable descriptions for stove states.
250
+ */
251
+ declare const StoveStateDescriptions: Record<number, string>;
252
+ /**
253
+ * Get description for a stove state, with fallback for unknown values.
254
+ */
255
+ declare const getStoveStateDescription: (state: number) => string;
164
256
  interface DeviceInfoType {
165
257
  status: StatusType;
166
258
  nvm: {
@@ -224,5 +316,5 @@ interface DiscoveredDevice {
224
316
  /** Signal strength in dBm (optional, not all platforms provide this) */
225
317
  rssi?: number;
226
318
  }
227
- export type { AlarmEntryType, AlarmsLogType, BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, DiscoveredDevice, EditDeviceAssociationBody, GeneralFlagsType, PelletAutonomyType, PowerDistributionType, RegenerationDataType, ServiceCountersType, ServiceStatusType, StatusCountersType, StatusType, TemperaturesType, TotalCountersType, UsageAnalyticsType, UserParametersType, };
228
- export { AlarmCode, AlarmDescriptions };
319
+ export type { AlarmEntryType, AlarmsLogType, BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, DiscoveredDevice, EditDeviceAssociationBody, FansType, GeneralFlagsType, PelletAutonomyType, PowerDistributionType, RegenerationDataType, ServiceCountersType, ServiceStatusType, StateType, StatusCountersType, StatusType, TemperaturesType, TotalCountersType, UsageAnalyticsType, UserParametersType, };
320
+ export { AlarmCode, AlarmDescriptions, getIgnitionSubPhaseDescription, getOperationalPhaseDescription, getStoveStateDescription, IgnitionSubPhase, IgnitionSubPhaseDescriptions, OperationalPhase, OperationalPhaseDescriptions, StoveState, StoveStateDescriptions, };