edilkamin 1.10.1 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/index.d.ts +1 -1
- package/dist/cjs/src/index.js +2 -1
- package/dist/cjs/src/library.d.ts +16 -0
- package/dist/cjs/src/library.js +59 -40
- package/dist/cjs/src/library.test.js +133 -0
- package/dist/esm/package.json +1 -1
- package/dist/esm/src/index.d.ts +1 -1
- package/dist/esm/src/index.js +1 -1
- package/dist/esm/src/library.d.ts +16 -0
- package/dist/esm/src/library.js +57 -39
- package/dist/esm/src/library.test.js +134 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/library.test.ts +173 -1
- package/src/library.ts +69 -48
package/dist/cjs/package.json
CHANGED
package/dist/cjs/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, getSession, signIn } from "./library";
|
|
4
|
+
export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
|
|
5
5
|
export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
|
|
6
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
7
|
export { AlarmCode, AlarmDescriptions } from "./types";
|
package/dist/cjs/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.setTargetTemperature = exports.getTargetTemperature = exports.getEnvironmentTemperature = exports.getPower = exports.setPowerOn = exports.setPowerOff = exports.setPower = exports.editDevice = exports.registerDevice = exports.deviceInfo = exports.AlarmDescriptions = exports.AlarmCode = exports.serialNumberToHex = exports.serialNumberFromHex = exports.serialNumberDisplay = exports.signIn = exports.getSession = exports.configure = exports.OLD_API_URL = exports.NEW_API_URL = exports.API_URL = exports.processResponse = exports.isBuffer = exports.decompressBuffer = exports.bleToWifiMac = void 0;
|
|
4
|
+
exports.setTargetTemperature = exports.getTargetTemperature = exports.getEnvironmentTemperature = exports.getPower = exports.setPowerOn = exports.setPowerOff = exports.setPower = exports.editDevice = exports.registerDevice = exports.deviceInfo = exports.AlarmDescriptions = exports.AlarmCode = exports.serialNumberToHex = exports.serialNumberFromHex = exports.serialNumberDisplay = exports.signIn = exports.getSession = exports.deriveUsageAnalytics = exports.configure = exports.OLD_API_URL = exports.NEW_API_URL = exports.API_URL = exports.processResponse = exports.isBuffer = exports.decompressBuffer = exports.bleToWifiMac = void 0;
|
|
5
5
|
const library_1 = require("./library");
|
|
6
6
|
var bluetooth_utils_1 = require("./bluetooth-utils");
|
|
7
7
|
Object.defineProperty(exports, "bleToWifiMac", { enumerable: true, get: function () { return bluetooth_utils_1.bleToWifiMac; } });
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "NEW_API_URL", { enumerable: true, get: function
|
|
|
15
15
|
Object.defineProperty(exports, "OLD_API_URL", { enumerable: true, get: function () { return constants_1.OLD_API_URL; } });
|
|
16
16
|
var library_2 = require("./library");
|
|
17
17
|
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return library_2.configure; } });
|
|
18
|
+
Object.defineProperty(exports, "deriveUsageAnalytics", { enumerable: true, get: function () { return library_2.deriveUsageAnalytics; } });
|
|
18
19
|
Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return library_2.getSession; } });
|
|
19
20
|
Object.defineProperty(exports, "signIn", { enumerable: true, get: function () { return library_2.signIn; } });
|
|
20
21
|
var serial_utils_1 = require("./serial-utils");
|
|
@@ -29,6 +29,22 @@ declare const createAuthService: (auth: typeof amplifyAuth) => {
|
|
|
29
29
|
getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
|
|
30
30
|
};
|
|
31
31
|
declare const signIn: (username: string, password: string, legacy?: boolean) => Promise<string>, getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Derives usage analytics from an existing DeviceInfo response.
|
|
34
|
+
* This is a pure function that performs client-side calculations without API calls.
|
|
35
|
+
*
|
|
36
|
+
* Use this when you already have a DeviceInfo object (e.g., from a previous deviceInfo() call)
|
|
37
|
+
* to avoid making an additional API request.
|
|
38
|
+
*
|
|
39
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
40
|
+
* @param {number} [serviceThreshold=2000] - Service threshold in hours.
|
|
41
|
+
* @returns {UsageAnalyticsType} - Comprehensive usage analytics.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const info = await api.deviceInfo(token, mac);
|
|
45
|
+
* const analytics = deriveUsageAnalytics(info);
|
|
46
|
+
*/
|
|
47
|
+
export declare const deriveUsageAnalytics: (deviceInfo: DeviceInfoType, serviceThreshold?: number) => UsageAnalyticsType;
|
|
32
48
|
/**
|
|
33
49
|
* Configures the library for API interactions.
|
|
34
50
|
* Initializes API methods with a specified base URL.
|
package/dist/cjs/src/library.js
CHANGED
|
@@ -42,7 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.signIn = exports.headers = exports.getSession = exports.createAuthService = exports.configureAmplify = exports.configure = void 0;
|
|
45
|
+
exports.signIn = exports.headers = exports.getSession = exports.createAuthService = exports.configureAmplify = exports.configure = 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"));
|
|
@@ -584,6 +584,63 @@ const getServiceTime = (baseURL) =>
|
|
|
584
584
|
* Most devices use 2000 hours.
|
|
585
585
|
*/
|
|
586
586
|
const DEFAULT_SERVICE_THRESHOLD = 2000;
|
|
587
|
+
/**
|
|
588
|
+
* Derives usage analytics from an existing DeviceInfo response.
|
|
589
|
+
* This is a pure function that performs client-side calculations without API calls.
|
|
590
|
+
*
|
|
591
|
+
* Use this when you already have a DeviceInfo object (e.g., from a previous deviceInfo() call)
|
|
592
|
+
* to avoid making an additional API request.
|
|
593
|
+
*
|
|
594
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
595
|
+
* @param {number} [serviceThreshold=2000] - Service threshold in hours.
|
|
596
|
+
* @returns {UsageAnalyticsType} - Comprehensive usage analytics.
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* const info = await api.deviceInfo(token, mac);
|
|
600
|
+
* const analytics = deriveUsageAnalytics(info);
|
|
601
|
+
*/
|
|
602
|
+
const deriveUsageAnalytics = (deviceInfo, serviceThreshold = DEFAULT_SERVICE_THRESHOLD) => {
|
|
603
|
+
const totalCounters = deviceInfo.nvm.total_counters;
|
|
604
|
+
const serviceCounters = deviceInfo.nvm.service_counters;
|
|
605
|
+
const regeneration = deviceInfo.nvm.regeneration;
|
|
606
|
+
const alarmsLog = deviceInfo.nvm.alarms_log;
|
|
607
|
+
const totalOperatingHours = totalCounters.p1_working_time +
|
|
608
|
+
totalCounters.p2_working_time +
|
|
609
|
+
totalCounters.p3_working_time +
|
|
610
|
+
totalCounters.p4_working_time +
|
|
611
|
+
totalCounters.p5_working_time;
|
|
612
|
+
const hoursSinceService = serviceCounters.p1_working_time +
|
|
613
|
+
serviceCounters.p2_working_time +
|
|
614
|
+
serviceCounters.p3_working_time +
|
|
615
|
+
serviceCounters.p4_working_time +
|
|
616
|
+
serviceCounters.p5_working_time;
|
|
617
|
+
const powerDistribution = totalOperatingHours === 0
|
|
618
|
+
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
619
|
+
: {
|
|
620
|
+
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
621
|
+
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
622
|
+
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
623
|
+
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
624
|
+
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
625
|
+
};
|
|
626
|
+
return {
|
|
627
|
+
totalPowerOns: totalCounters.power_ons,
|
|
628
|
+
totalOperatingHours,
|
|
629
|
+
powerDistribution,
|
|
630
|
+
serviceStatus: {
|
|
631
|
+
totalServiceHours: deviceInfo.status.counters.service_time,
|
|
632
|
+
hoursSinceService,
|
|
633
|
+
serviceThresholdHours: serviceThreshold,
|
|
634
|
+
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
635
|
+
},
|
|
636
|
+
blackoutCount: regeneration.blackout_counter,
|
|
637
|
+
lastMaintenanceDate: regeneration.last_intervention > 0
|
|
638
|
+
? new Date(regeneration.last_intervention * 1000)
|
|
639
|
+
: null,
|
|
640
|
+
alarmCount: alarmsLog.number,
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
exports.deriveUsageAnalytics = deriveUsageAnalytics;
|
|
587
644
|
const getTotalOperatingHours = (baseURL) =>
|
|
588
645
|
/**
|
|
589
646
|
* Calculates total operating hours across all power levels.
|
|
@@ -662,45 +719,7 @@ const getUsageAnalytics = (baseURL) =>
|
|
|
662
719
|
*/
|
|
663
720
|
(jwtToken_1, macAddress_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, ...args_1], void 0, function* (jwtToken, macAddress, serviceThreshold = DEFAULT_SERVICE_THRESHOLD) {
|
|
664
721
|
const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
|
|
665
|
-
|
|
666
|
-
const serviceCounters = info.nvm.service_counters;
|
|
667
|
-
const regeneration = info.nvm.regeneration;
|
|
668
|
-
const alarmsLog = info.nvm.alarms_log;
|
|
669
|
-
const totalOperatingHours = totalCounters.p1_working_time +
|
|
670
|
-
totalCounters.p2_working_time +
|
|
671
|
-
totalCounters.p3_working_time +
|
|
672
|
-
totalCounters.p4_working_time +
|
|
673
|
-
totalCounters.p5_working_time;
|
|
674
|
-
const hoursSinceService = serviceCounters.p1_working_time +
|
|
675
|
-
serviceCounters.p2_working_time +
|
|
676
|
-
serviceCounters.p3_working_time +
|
|
677
|
-
serviceCounters.p4_working_time +
|
|
678
|
-
serviceCounters.p5_working_time;
|
|
679
|
-
const powerDistribution = totalOperatingHours === 0
|
|
680
|
-
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
681
|
-
: {
|
|
682
|
-
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
683
|
-
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
684
|
-
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
685
|
-
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
686
|
-
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
687
|
-
};
|
|
688
|
-
return {
|
|
689
|
-
totalPowerOns: totalCounters.power_ons,
|
|
690
|
-
totalOperatingHours,
|
|
691
|
-
powerDistribution,
|
|
692
|
-
serviceStatus: {
|
|
693
|
-
totalServiceHours: info.status.counters.service_time,
|
|
694
|
-
hoursSinceService,
|
|
695
|
-
serviceThresholdHours: serviceThreshold,
|
|
696
|
-
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
697
|
-
},
|
|
698
|
-
blackoutCount: regeneration.blackout_counter,
|
|
699
|
-
lastMaintenanceDate: regeneration.last_intervention > 0
|
|
700
|
-
? new Date(regeneration.last_intervention * 1000)
|
|
701
|
-
: null,
|
|
702
|
-
alarmCount: alarmsLog.number,
|
|
703
|
-
};
|
|
722
|
+
return (0, exports.deriveUsageAnalytics)(info, serviceThreshold);
|
|
704
723
|
});
|
|
705
724
|
const registerDevice = (baseURL) =>
|
|
706
725
|
/**
|
|
@@ -1014,6 +1014,139 @@ describe("library", () => {
|
|
|
1014
1014
|
assert_1.strict.equal(result.lastMaintenanceDate, null);
|
|
1015
1015
|
}));
|
|
1016
1016
|
});
|
|
1017
|
+
describe("deriveUsageAnalytics", () => {
|
|
1018
|
+
const mockDeviceInfoForDerive = {
|
|
1019
|
+
status: {
|
|
1020
|
+
commands: {
|
|
1021
|
+
power: false,
|
|
1022
|
+
},
|
|
1023
|
+
temperatures: {
|
|
1024
|
+
enviroment: 20,
|
|
1025
|
+
set_air: 21,
|
|
1026
|
+
get_air: 20,
|
|
1027
|
+
set_water: 40,
|
|
1028
|
+
get_water: 35,
|
|
1029
|
+
},
|
|
1030
|
+
counters: {
|
|
1031
|
+
service_time: 1108,
|
|
1032
|
+
},
|
|
1033
|
+
flags: {
|
|
1034
|
+
is_pellet_in_reserve: false,
|
|
1035
|
+
},
|
|
1036
|
+
pellet: {
|
|
1037
|
+
autonomy_time: 180,
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
nvm: {
|
|
1041
|
+
user_parameters: {
|
|
1042
|
+
language: 1,
|
|
1043
|
+
is_auto: false,
|
|
1044
|
+
is_fahrenheit: false,
|
|
1045
|
+
is_sound_active: false,
|
|
1046
|
+
enviroment_1_temperature: 19,
|
|
1047
|
+
enviroment_2_temperature: 20,
|
|
1048
|
+
enviroment_3_temperature: 20,
|
|
1049
|
+
manual_power: 1,
|
|
1050
|
+
fan_1_ventilation: 3,
|
|
1051
|
+
fan_2_ventilation: 0,
|
|
1052
|
+
fan_3_ventilation: 0,
|
|
1053
|
+
is_standby_active: false,
|
|
1054
|
+
standby_waiting_time: 60,
|
|
1055
|
+
},
|
|
1056
|
+
total_counters: {
|
|
1057
|
+
power_ons: 278,
|
|
1058
|
+
p1_working_time: 833,
|
|
1059
|
+
p2_working_time: 15,
|
|
1060
|
+
p3_working_time: 19,
|
|
1061
|
+
p4_working_time: 8,
|
|
1062
|
+
p5_working_time: 17,
|
|
1063
|
+
},
|
|
1064
|
+
service_counters: {
|
|
1065
|
+
p1_working_time: 100,
|
|
1066
|
+
p2_working_time: 10,
|
|
1067
|
+
p3_working_time: 5,
|
|
1068
|
+
p4_working_time: 2,
|
|
1069
|
+
p5_working_time: 1,
|
|
1070
|
+
},
|
|
1071
|
+
regeneration: {
|
|
1072
|
+
time: 0,
|
|
1073
|
+
last_intervention: 1577836800,
|
|
1074
|
+
daylight_time_flag: 0,
|
|
1075
|
+
blackout_counter: 43,
|
|
1076
|
+
airkare_working_hours_counter: 0,
|
|
1077
|
+
},
|
|
1078
|
+
alarms_log: {
|
|
1079
|
+
number: 6,
|
|
1080
|
+
index: 6,
|
|
1081
|
+
alarms: [],
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
};
|
|
1085
|
+
it("should derive analytics from device info without API call", () => {
|
|
1086
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1087
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(mockDeviceInfoForDerive);
|
|
1088
|
+
assert_1.strict.equal(analytics.totalPowerOns, 278);
|
|
1089
|
+
assert_1.strict.equal(analytics.totalOperatingHours, 892); // 833+15+19+8+17
|
|
1090
|
+
assert_1.strict.equal(analytics.blackoutCount, 43);
|
|
1091
|
+
assert_1.strict.equal(analytics.alarmCount, 6);
|
|
1092
|
+
});
|
|
1093
|
+
it("should calculate power distribution correctly", () => {
|
|
1094
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1095
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(mockDeviceInfoForDerive);
|
|
1096
|
+
// P1: 833/892 ≈ 93.4%
|
|
1097
|
+
assert_1.strict.ok(analytics.powerDistribution.p1 > 93);
|
|
1098
|
+
assert_1.strict.ok(analytics.powerDistribution.p1 < 94);
|
|
1099
|
+
// Sum should be 100%
|
|
1100
|
+
const sum = Object.values(analytics.powerDistribution).reduce((a, b) => a + b, 0);
|
|
1101
|
+
assert_1.strict.ok(Math.abs(sum - 100) < 0.001);
|
|
1102
|
+
});
|
|
1103
|
+
it("should handle zero operating hours", () => {
|
|
1104
|
+
const zeroHoursInfo = Object.assign(Object.assign({}, mockDeviceInfoForDerive), { nvm: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm), { total_counters: {
|
|
1105
|
+
power_ons: 0,
|
|
1106
|
+
p1_working_time: 0,
|
|
1107
|
+
p2_working_time: 0,
|
|
1108
|
+
p3_working_time: 0,
|
|
1109
|
+
p4_working_time: 0,
|
|
1110
|
+
p5_working_time: 0,
|
|
1111
|
+
} }) });
|
|
1112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1113
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(zeroHoursInfo);
|
|
1114
|
+
assert_1.strict.deepEqual(analytics.powerDistribution, {
|
|
1115
|
+
p1: 0,
|
|
1116
|
+
p2: 0,
|
|
1117
|
+
p3: 0,
|
|
1118
|
+
p4: 0,
|
|
1119
|
+
p5: 0,
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
it("should respect custom service threshold", () => {
|
|
1123
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(
|
|
1124
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1125
|
+
mockDeviceInfoForDerive, 100);
|
|
1126
|
+
// 118 hours since service >= 100 threshold
|
|
1127
|
+
assert_1.strict.equal(analytics.serviceStatus.isServiceDue, true);
|
|
1128
|
+
assert_1.strict.equal(analytics.serviceStatus.serviceThresholdHours, 100);
|
|
1129
|
+
});
|
|
1130
|
+
it("should use default threshold of 2000 hours", () => {
|
|
1131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1132
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(mockDeviceInfoForDerive);
|
|
1133
|
+
assert_1.strict.equal(analytics.serviceStatus.serviceThresholdHours, 2000);
|
|
1134
|
+
assert_1.strict.equal(analytics.serviceStatus.isServiceDue, false); // 118 < 2000
|
|
1135
|
+
});
|
|
1136
|
+
it("should convert last_intervention timestamp to Date", () => {
|
|
1137
|
+
var _a;
|
|
1138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1139
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(mockDeviceInfoForDerive);
|
|
1140
|
+
assert_1.strict.ok(analytics.lastMaintenanceDate instanceof Date);
|
|
1141
|
+
assert_1.strict.equal((_a = analytics.lastMaintenanceDate) === null || _a === void 0 ? void 0 : _a.getTime(), 1577836800 * 1000);
|
|
1142
|
+
});
|
|
1143
|
+
it("should return null for lastMaintenanceDate when timestamp is 0", () => {
|
|
1144
|
+
const noMaintenanceInfo = Object.assign(Object.assign({}, mockDeviceInfoForDerive), { nvm: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm), { regeneration: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm.regeneration), { last_intervention: 0 }) }) });
|
|
1145
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1146
|
+
const analytics = (0, library_1.deriveUsageAnalytics)(noMaintenanceInfo);
|
|
1147
|
+
assert_1.strict.equal(analytics.lastMaintenanceDate, null);
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1017
1150
|
describe("Error Handling", () => {
|
|
1018
1151
|
const errorTests = [
|
|
1019
1152
|
{ status: 400, statusText: "Bad Request" },
|
package/dist/esm/package.json
CHANGED
package/dist/esm/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, getSession, signIn } from "./library";
|
|
4
|
+
export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
|
|
5
5
|
export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
|
|
6
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
7
|
export { AlarmCode, AlarmDescriptions } from "./types";
|
package/dist/esm/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@ 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, getSession, signIn } from "./library";
|
|
5
|
+
export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
|
|
6
6
|
export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
|
|
7
7
|
export { AlarmCode, AlarmDescriptions } from "./types";
|
|
8
8
|
export const { deviceInfo, registerDevice, editDevice, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
|
|
@@ -29,6 +29,22 @@ declare const createAuthService: (auth: typeof amplifyAuth) => {
|
|
|
29
29
|
getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
|
|
30
30
|
};
|
|
31
31
|
declare const signIn: (username: string, password: string, legacy?: boolean) => Promise<string>, getSession: (forceRefresh?: boolean, legacy?: boolean) => Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Derives usage analytics from an existing DeviceInfo response.
|
|
34
|
+
* This is a pure function that performs client-side calculations without API calls.
|
|
35
|
+
*
|
|
36
|
+
* Use this when you already have a DeviceInfo object (e.g., from a previous deviceInfo() call)
|
|
37
|
+
* to avoid making an additional API request.
|
|
38
|
+
*
|
|
39
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
40
|
+
* @param {number} [serviceThreshold=2000] - Service threshold in hours.
|
|
41
|
+
* @returns {UsageAnalyticsType} - Comprehensive usage analytics.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const info = await api.deviceInfo(token, mac);
|
|
45
|
+
* const analytics = deriveUsageAnalytics(info);
|
|
46
|
+
*/
|
|
47
|
+
export declare const deriveUsageAnalytics: (deviceInfo: DeviceInfoType, serviceThreshold?: number) => UsageAnalyticsType;
|
|
32
48
|
/**
|
|
33
49
|
* Configures the library for API interactions.
|
|
34
50
|
* Initializes API methods with a specified base URL.
|
package/dist/esm/src/library.js
CHANGED
|
@@ -543,6 +543,62 @@ const getServiceTime = (baseURL) =>
|
|
|
543
543
|
* Most devices use 2000 hours.
|
|
544
544
|
*/
|
|
545
545
|
const DEFAULT_SERVICE_THRESHOLD = 2000;
|
|
546
|
+
/**
|
|
547
|
+
* Derives usage analytics from an existing DeviceInfo response.
|
|
548
|
+
* This is a pure function that performs client-side calculations without API calls.
|
|
549
|
+
*
|
|
550
|
+
* Use this when you already have a DeviceInfo object (e.g., from a previous deviceInfo() call)
|
|
551
|
+
* to avoid making an additional API request.
|
|
552
|
+
*
|
|
553
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
554
|
+
* @param {number} [serviceThreshold=2000] - Service threshold in hours.
|
|
555
|
+
* @returns {UsageAnalyticsType} - Comprehensive usage analytics.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* const info = await api.deviceInfo(token, mac);
|
|
559
|
+
* const analytics = deriveUsageAnalytics(info);
|
|
560
|
+
*/
|
|
561
|
+
export const deriveUsageAnalytics = (deviceInfo, serviceThreshold = DEFAULT_SERVICE_THRESHOLD) => {
|
|
562
|
+
const totalCounters = deviceInfo.nvm.total_counters;
|
|
563
|
+
const serviceCounters = deviceInfo.nvm.service_counters;
|
|
564
|
+
const regeneration = deviceInfo.nvm.regeneration;
|
|
565
|
+
const alarmsLog = deviceInfo.nvm.alarms_log;
|
|
566
|
+
const totalOperatingHours = totalCounters.p1_working_time +
|
|
567
|
+
totalCounters.p2_working_time +
|
|
568
|
+
totalCounters.p3_working_time +
|
|
569
|
+
totalCounters.p4_working_time +
|
|
570
|
+
totalCounters.p5_working_time;
|
|
571
|
+
const hoursSinceService = serviceCounters.p1_working_time +
|
|
572
|
+
serviceCounters.p2_working_time +
|
|
573
|
+
serviceCounters.p3_working_time +
|
|
574
|
+
serviceCounters.p4_working_time +
|
|
575
|
+
serviceCounters.p5_working_time;
|
|
576
|
+
const powerDistribution = totalOperatingHours === 0
|
|
577
|
+
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
578
|
+
: {
|
|
579
|
+
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
580
|
+
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
581
|
+
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
582
|
+
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
583
|
+
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
totalPowerOns: totalCounters.power_ons,
|
|
587
|
+
totalOperatingHours,
|
|
588
|
+
powerDistribution,
|
|
589
|
+
serviceStatus: {
|
|
590
|
+
totalServiceHours: deviceInfo.status.counters.service_time,
|
|
591
|
+
hoursSinceService,
|
|
592
|
+
serviceThresholdHours: serviceThreshold,
|
|
593
|
+
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
594
|
+
},
|
|
595
|
+
blackoutCount: regeneration.blackout_counter,
|
|
596
|
+
lastMaintenanceDate: regeneration.last_intervention > 0
|
|
597
|
+
? new Date(regeneration.last_intervention * 1000)
|
|
598
|
+
: null,
|
|
599
|
+
alarmCount: alarmsLog.number,
|
|
600
|
+
};
|
|
601
|
+
};
|
|
546
602
|
const getTotalOperatingHours = (baseURL) =>
|
|
547
603
|
/**
|
|
548
604
|
* Calculates total operating hours across all power levels.
|
|
@@ -621,45 +677,7 @@ const getUsageAnalytics = (baseURL) =>
|
|
|
621
677
|
*/
|
|
622
678
|
(jwtToken_1, macAddress_1, ...args_1) => __awaiter(void 0, [jwtToken_1, macAddress_1, ...args_1], void 0, function* (jwtToken, macAddress, serviceThreshold = DEFAULT_SERVICE_THRESHOLD) {
|
|
623
679
|
const info = yield deviceInfo(baseURL)(jwtToken, macAddress);
|
|
624
|
-
|
|
625
|
-
const serviceCounters = info.nvm.service_counters;
|
|
626
|
-
const regeneration = info.nvm.regeneration;
|
|
627
|
-
const alarmsLog = info.nvm.alarms_log;
|
|
628
|
-
const totalOperatingHours = totalCounters.p1_working_time +
|
|
629
|
-
totalCounters.p2_working_time +
|
|
630
|
-
totalCounters.p3_working_time +
|
|
631
|
-
totalCounters.p4_working_time +
|
|
632
|
-
totalCounters.p5_working_time;
|
|
633
|
-
const hoursSinceService = serviceCounters.p1_working_time +
|
|
634
|
-
serviceCounters.p2_working_time +
|
|
635
|
-
serviceCounters.p3_working_time +
|
|
636
|
-
serviceCounters.p4_working_time +
|
|
637
|
-
serviceCounters.p5_working_time;
|
|
638
|
-
const powerDistribution = totalOperatingHours === 0
|
|
639
|
-
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
640
|
-
: {
|
|
641
|
-
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
642
|
-
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
643
|
-
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
644
|
-
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
645
|
-
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
646
|
-
};
|
|
647
|
-
return {
|
|
648
|
-
totalPowerOns: totalCounters.power_ons,
|
|
649
|
-
totalOperatingHours,
|
|
650
|
-
powerDistribution,
|
|
651
|
-
serviceStatus: {
|
|
652
|
-
totalServiceHours: info.status.counters.service_time,
|
|
653
|
-
hoursSinceService,
|
|
654
|
-
serviceThresholdHours: serviceThreshold,
|
|
655
|
-
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
656
|
-
},
|
|
657
|
-
blackoutCount: regeneration.blackout_counter,
|
|
658
|
-
lastMaintenanceDate: regeneration.last_intervention > 0
|
|
659
|
-
? new Date(regeneration.last_intervention * 1000)
|
|
660
|
-
: null,
|
|
661
|
-
alarmCount: alarmsLog.number,
|
|
662
|
-
};
|
|
680
|
+
return deriveUsageAnalytics(info, serviceThreshold);
|
|
663
681
|
});
|
|
664
682
|
const registerDevice = (baseURL) =>
|
|
665
683
|
/**
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { strict as assert } from "assert";
|
|
11
11
|
import pako from "pako";
|
|
12
12
|
import sinon from "sinon";
|
|
13
|
-
import { configure, createAuthService } from "../src/library";
|
|
13
|
+
import { configure, createAuthService, deriveUsageAnalytics, } from "../src/library";
|
|
14
14
|
import { API_URL } from "./constants";
|
|
15
15
|
/**
|
|
16
16
|
* Helper to create a gzip-compressed Buffer object for testing.
|
|
@@ -1009,6 +1009,139 @@ describe("library", () => {
|
|
|
1009
1009
|
assert.equal(result.lastMaintenanceDate, null);
|
|
1010
1010
|
}));
|
|
1011
1011
|
});
|
|
1012
|
+
describe("deriveUsageAnalytics", () => {
|
|
1013
|
+
const mockDeviceInfoForDerive = {
|
|
1014
|
+
status: {
|
|
1015
|
+
commands: {
|
|
1016
|
+
power: false,
|
|
1017
|
+
},
|
|
1018
|
+
temperatures: {
|
|
1019
|
+
enviroment: 20,
|
|
1020
|
+
set_air: 21,
|
|
1021
|
+
get_air: 20,
|
|
1022
|
+
set_water: 40,
|
|
1023
|
+
get_water: 35,
|
|
1024
|
+
},
|
|
1025
|
+
counters: {
|
|
1026
|
+
service_time: 1108,
|
|
1027
|
+
},
|
|
1028
|
+
flags: {
|
|
1029
|
+
is_pellet_in_reserve: false,
|
|
1030
|
+
},
|
|
1031
|
+
pellet: {
|
|
1032
|
+
autonomy_time: 180,
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
nvm: {
|
|
1036
|
+
user_parameters: {
|
|
1037
|
+
language: 1,
|
|
1038
|
+
is_auto: false,
|
|
1039
|
+
is_fahrenheit: false,
|
|
1040
|
+
is_sound_active: false,
|
|
1041
|
+
enviroment_1_temperature: 19,
|
|
1042
|
+
enviroment_2_temperature: 20,
|
|
1043
|
+
enviroment_3_temperature: 20,
|
|
1044
|
+
manual_power: 1,
|
|
1045
|
+
fan_1_ventilation: 3,
|
|
1046
|
+
fan_2_ventilation: 0,
|
|
1047
|
+
fan_3_ventilation: 0,
|
|
1048
|
+
is_standby_active: false,
|
|
1049
|
+
standby_waiting_time: 60,
|
|
1050
|
+
},
|
|
1051
|
+
total_counters: {
|
|
1052
|
+
power_ons: 278,
|
|
1053
|
+
p1_working_time: 833,
|
|
1054
|
+
p2_working_time: 15,
|
|
1055
|
+
p3_working_time: 19,
|
|
1056
|
+
p4_working_time: 8,
|
|
1057
|
+
p5_working_time: 17,
|
|
1058
|
+
},
|
|
1059
|
+
service_counters: {
|
|
1060
|
+
p1_working_time: 100,
|
|
1061
|
+
p2_working_time: 10,
|
|
1062
|
+
p3_working_time: 5,
|
|
1063
|
+
p4_working_time: 2,
|
|
1064
|
+
p5_working_time: 1,
|
|
1065
|
+
},
|
|
1066
|
+
regeneration: {
|
|
1067
|
+
time: 0,
|
|
1068
|
+
last_intervention: 1577836800,
|
|
1069
|
+
daylight_time_flag: 0,
|
|
1070
|
+
blackout_counter: 43,
|
|
1071
|
+
airkare_working_hours_counter: 0,
|
|
1072
|
+
},
|
|
1073
|
+
alarms_log: {
|
|
1074
|
+
number: 6,
|
|
1075
|
+
index: 6,
|
|
1076
|
+
alarms: [],
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
};
|
|
1080
|
+
it("should derive analytics from device info without API call", () => {
|
|
1081
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1082
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive);
|
|
1083
|
+
assert.equal(analytics.totalPowerOns, 278);
|
|
1084
|
+
assert.equal(analytics.totalOperatingHours, 892); // 833+15+19+8+17
|
|
1085
|
+
assert.equal(analytics.blackoutCount, 43);
|
|
1086
|
+
assert.equal(analytics.alarmCount, 6);
|
|
1087
|
+
});
|
|
1088
|
+
it("should calculate power distribution correctly", () => {
|
|
1089
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1090
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive);
|
|
1091
|
+
// P1: 833/892 ≈ 93.4%
|
|
1092
|
+
assert.ok(analytics.powerDistribution.p1 > 93);
|
|
1093
|
+
assert.ok(analytics.powerDistribution.p1 < 94);
|
|
1094
|
+
// Sum should be 100%
|
|
1095
|
+
const sum = Object.values(analytics.powerDistribution).reduce((a, b) => a + b, 0);
|
|
1096
|
+
assert.ok(Math.abs(sum - 100) < 0.001);
|
|
1097
|
+
});
|
|
1098
|
+
it("should handle zero operating hours", () => {
|
|
1099
|
+
const zeroHoursInfo = Object.assign(Object.assign({}, mockDeviceInfoForDerive), { nvm: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm), { total_counters: {
|
|
1100
|
+
power_ons: 0,
|
|
1101
|
+
p1_working_time: 0,
|
|
1102
|
+
p2_working_time: 0,
|
|
1103
|
+
p3_working_time: 0,
|
|
1104
|
+
p4_working_time: 0,
|
|
1105
|
+
p5_working_time: 0,
|
|
1106
|
+
} }) });
|
|
1107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1108
|
+
const analytics = deriveUsageAnalytics(zeroHoursInfo);
|
|
1109
|
+
assert.deepEqual(analytics.powerDistribution, {
|
|
1110
|
+
p1: 0,
|
|
1111
|
+
p2: 0,
|
|
1112
|
+
p3: 0,
|
|
1113
|
+
p4: 0,
|
|
1114
|
+
p5: 0,
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
it("should respect custom service threshold", () => {
|
|
1118
|
+
const analytics = deriveUsageAnalytics(
|
|
1119
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1120
|
+
mockDeviceInfoForDerive, 100);
|
|
1121
|
+
// 118 hours since service >= 100 threshold
|
|
1122
|
+
assert.equal(analytics.serviceStatus.isServiceDue, true);
|
|
1123
|
+
assert.equal(analytics.serviceStatus.serviceThresholdHours, 100);
|
|
1124
|
+
});
|
|
1125
|
+
it("should use default threshold of 2000 hours", () => {
|
|
1126
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1127
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive);
|
|
1128
|
+
assert.equal(analytics.serviceStatus.serviceThresholdHours, 2000);
|
|
1129
|
+
assert.equal(analytics.serviceStatus.isServiceDue, false); // 118 < 2000
|
|
1130
|
+
});
|
|
1131
|
+
it("should convert last_intervention timestamp to Date", () => {
|
|
1132
|
+
var _a;
|
|
1133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1134
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive);
|
|
1135
|
+
assert.ok(analytics.lastMaintenanceDate instanceof Date);
|
|
1136
|
+
assert.equal((_a = analytics.lastMaintenanceDate) === null || _a === void 0 ? void 0 : _a.getTime(), 1577836800 * 1000);
|
|
1137
|
+
});
|
|
1138
|
+
it("should return null for lastMaintenanceDate when timestamp is 0", () => {
|
|
1139
|
+
const noMaintenanceInfo = Object.assign(Object.assign({}, mockDeviceInfoForDerive), { nvm: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm), { regeneration: Object.assign(Object.assign({}, mockDeviceInfoForDerive.nvm.regeneration), { last_intervention: 0 }) }) });
|
|
1140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1141
|
+
const analytics = deriveUsageAnalytics(noMaintenanceInfo);
|
|
1142
|
+
assert.equal(analytics.lastMaintenanceDate, null);
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1012
1145
|
describe("Error Handling", () => {
|
|
1013
1146
|
const errorTests = [
|
|
1014
1147
|
{ status: 400, statusText: "Bad Request" },
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { configure } from "./library";
|
|
|
3
3
|
export { bleToWifiMac } from "./bluetooth-utils";
|
|
4
4
|
export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
|
|
5
5
|
export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
|
|
6
|
-
export { configure, getSession, signIn } from "./library";
|
|
6
|
+
export { configure, deriveUsageAnalytics, getSession, signIn } from "./library";
|
|
7
7
|
export {
|
|
8
8
|
serialNumberDisplay,
|
|
9
9
|
serialNumberFromHex,
|
package/src/library.test.ts
CHANGED
|
@@ -3,7 +3,11 @@ import * as amplifyAuth from "aws-amplify/auth";
|
|
|
3
3
|
import pako from "pako";
|
|
4
4
|
import sinon from "sinon";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
configure,
|
|
8
|
+
createAuthService,
|
|
9
|
+
deriveUsageAnalytics,
|
|
10
|
+
} from "../src/library";
|
|
7
11
|
import { API_URL } from "./constants";
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -1307,6 +1311,174 @@ describe("library", () => {
|
|
|
1307
1311
|
});
|
|
1308
1312
|
});
|
|
1309
1313
|
|
|
1314
|
+
describe("deriveUsageAnalytics", () => {
|
|
1315
|
+
const mockDeviceInfoForDerive = {
|
|
1316
|
+
status: {
|
|
1317
|
+
commands: {
|
|
1318
|
+
power: false,
|
|
1319
|
+
},
|
|
1320
|
+
temperatures: {
|
|
1321
|
+
enviroment: 20,
|
|
1322
|
+
set_air: 21,
|
|
1323
|
+
get_air: 20,
|
|
1324
|
+
set_water: 40,
|
|
1325
|
+
get_water: 35,
|
|
1326
|
+
},
|
|
1327
|
+
counters: {
|
|
1328
|
+
service_time: 1108,
|
|
1329
|
+
},
|
|
1330
|
+
flags: {
|
|
1331
|
+
is_pellet_in_reserve: false,
|
|
1332
|
+
},
|
|
1333
|
+
pellet: {
|
|
1334
|
+
autonomy_time: 180,
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
nvm: {
|
|
1338
|
+
user_parameters: {
|
|
1339
|
+
language: 1,
|
|
1340
|
+
is_auto: false,
|
|
1341
|
+
is_fahrenheit: false,
|
|
1342
|
+
is_sound_active: false,
|
|
1343
|
+
enviroment_1_temperature: 19,
|
|
1344
|
+
enviroment_2_temperature: 20,
|
|
1345
|
+
enviroment_3_temperature: 20,
|
|
1346
|
+
manual_power: 1,
|
|
1347
|
+
fan_1_ventilation: 3,
|
|
1348
|
+
fan_2_ventilation: 0,
|
|
1349
|
+
fan_3_ventilation: 0,
|
|
1350
|
+
is_standby_active: false,
|
|
1351
|
+
standby_waiting_time: 60,
|
|
1352
|
+
},
|
|
1353
|
+
total_counters: {
|
|
1354
|
+
power_ons: 278,
|
|
1355
|
+
p1_working_time: 833,
|
|
1356
|
+
p2_working_time: 15,
|
|
1357
|
+
p3_working_time: 19,
|
|
1358
|
+
p4_working_time: 8,
|
|
1359
|
+
p5_working_time: 17,
|
|
1360
|
+
},
|
|
1361
|
+
service_counters: {
|
|
1362
|
+
p1_working_time: 100,
|
|
1363
|
+
p2_working_time: 10,
|
|
1364
|
+
p3_working_time: 5,
|
|
1365
|
+
p4_working_time: 2,
|
|
1366
|
+
p5_working_time: 1,
|
|
1367
|
+
},
|
|
1368
|
+
regeneration: {
|
|
1369
|
+
time: 0,
|
|
1370
|
+
last_intervention: 1577836800,
|
|
1371
|
+
daylight_time_flag: 0,
|
|
1372
|
+
blackout_counter: 43,
|
|
1373
|
+
airkare_working_hours_counter: 0,
|
|
1374
|
+
},
|
|
1375
|
+
alarms_log: {
|
|
1376
|
+
number: 6,
|
|
1377
|
+
index: 6,
|
|
1378
|
+
alarms: [],
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
it("should derive analytics from device info without API call", () => {
|
|
1384
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1385
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive as any);
|
|
1386
|
+
|
|
1387
|
+
assert.equal(analytics.totalPowerOns, 278);
|
|
1388
|
+
assert.equal(analytics.totalOperatingHours, 892); // 833+15+19+8+17
|
|
1389
|
+
assert.equal(analytics.blackoutCount, 43);
|
|
1390
|
+
assert.equal(analytics.alarmCount, 6);
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
it("should calculate power distribution correctly", () => {
|
|
1394
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1395
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive as any);
|
|
1396
|
+
|
|
1397
|
+
// P1: 833/892 ≈ 93.4%
|
|
1398
|
+
assert.ok(analytics.powerDistribution.p1 > 93);
|
|
1399
|
+
assert.ok(analytics.powerDistribution.p1 < 94);
|
|
1400
|
+
|
|
1401
|
+
// Sum should be 100%
|
|
1402
|
+
const sum = Object.values(analytics.powerDistribution).reduce(
|
|
1403
|
+
(a, b) => a + b,
|
|
1404
|
+
0,
|
|
1405
|
+
);
|
|
1406
|
+
assert.ok(Math.abs(sum - 100) < 0.001);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
it("should handle zero operating hours", () => {
|
|
1410
|
+
const zeroHoursInfo = {
|
|
1411
|
+
...mockDeviceInfoForDerive,
|
|
1412
|
+
nvm: {
|
|
1413
|
+
...mockDeviceInfoForDerive.nvm,
|
|
1414
|
+
total_counters: {
|
|
1415
|
+
power_ons: 0,
|
|
1416
|
+
p1_working_time: 0,
|
|
1417
|
+
p2_working_time: 0,
|
|
1418
|
+
p3_working_time: 0,
|
|
1419
|
+
p4_working_time: 0,
|
|
1420
|
+
p5_working_time: 0,
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1426
|
+
const analytics = deriveUsageAnalytics(zeroHoursInfo as any);
|
|
1427
|
+
assert.deepEqual(analytics.powerDistribution, {
|
|
1428
|
+
p1: 0,
|
|
1429
|
+
p2: 0,
|
|
1430
|
+
p3: 0,
|
|
1431
|
+
p4: 0,
|
|
1432
|
+
p5: 0,
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
it("should respect custom service threshold", () => {
|
|
1437
|
+
const analytics = deriveUsageAnalytics(
|
|
1438
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1439
|
+
mockDeviceInfoForDerive as any,
|
|
1440
|
+
100,
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
// 118 hours since service >= 100 threshold
|
|
1444
|
+
assert.equal(analytics.serviceStatus.isServiceDue, true);
|
|
1445
|
+
assert.equal(analytics.serviceStatus.serviceThresholdHours, 100);
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
it("should use default threshold of 2000 hours", () => {
|
|
1449
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1450
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive as any);
|
|
1451
|
+
|
|
1452
|
+
assert.equal(analytics.serviceStatus.serviceThresholdHours, 2000);
|
|
1453
|
+
assert.equal(analytics.serviceStatus.isServiceDue, false); // 118 < 2000
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
it("should convert last_intervention timestamp to Date", () => {
|
|
1457
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1458
|
+
const analytics = deriveUsageAnalytics(mockDeviceInfoForDerive as any);
|
|
1459
|
+
|
|
1460
|
+
assert.ok(analytics.lastMaintenanceDate instanceof Date);
|
|
1461
|
+
assert.equal(analytics.lastMaintenanceDate?.getTime(), 1577836800 * 1000);
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
it("should return null for lastMaintenanceDate when timestamp is 0", () => {
|
|
1465
|
+
const noMaintenanceInfo = {
|
|
1466
|
+
...mockDeviceInfoForDerive,
|
|
1467
|
+
nvm: {
|
|
1468
|
+
...mockDeviceInfoForDerive.nvm,
|
|
1469
|
+
regeneration: {
|
|
1470
|
+
...mockDeviceInfoForDerive.nvm.regeneration,
|
|
1471
|
+
last_intervention: 0,
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1477
|
+
const analytics = deriveUsageAnalytics(noMaintenanceInfo as any);
|
|
1478
|
+
assert.equal(analytics.lastMaintenanceDate, null);
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1310
1482
|
describe("Error Handling", () => {
|
|
1311
1483
|
const errorTests = [
|
|
1312
1484
|
{ status: 400, statusText: "Bad Request" },
|
package/src/library.ts
CHANGED
|
@@ -718,6 +718,74 @@ const getServiceTime =
|
|
|
718
718
|
*/
|
|
719
719
|
const DEFAULT_SERVICE_THRESHOLD = 2000;
|
|
720
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Derives usage analytics from an existing DeviceInfo response.
|
|
723
|
+
* This is a pure function that performs client-side calculations without API calls.
|
|
724
|
+
*
|
|
725
|
+
* Use this when you already have a DeviceInfo object (e.g., from a previous deviceInfo() call)
|
|
726
|
+
* to avoid making an additional API request.
|
|
727
|
+
*
|
|
728
|
+
* @param {DeviceInfoType} deviceInfo - The device info response object.
|
|
729
|
+
* @param {number} [serviceThreshold=2000] - Service threshold in hours.
|
|
730
|
+
* @returns {UsageAnalyticsType} - Comprehensive usage analytics.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* const info = await api.deviceInfo(token, mac);
|
|
734
|
+
* const analytics = deriveUsageAnalytics(info);
|
|
735
|
+
*/
|
|
736
|
+
export const deriveUsageAnalytics = (
|
|
737
|
+
deviceInfo: DeviceInfoType,
|
|
738
|
+
serviceThreshold: number = DEFAULT_SERVICE_THRESHOLD,
|
|
739
|
+
): UsageAnalyticsType => {
|
|
740
|
+
const totalCounters = deviceInfo.nvm.total_counters;
|
|
741
|
+
const serviceCounters = deviceInfo.nvm.service_counters;
|
|
742
|
+
const regeneration = deviceInfo.nvm.regeneration;
|
|
743
|
+
const alarmsLog = deviceInfo.nvm.alarms_log;
|
|
744
|
+
|
|
745
|
+
const totalOperatingHours =
|
|
746
|
+
totalCounters.p1_working_time +
|
|
747
|
+
totalCounters.p2_working_time +
|
|
748
|
+
totalCounters.p3_working_time +
|
|
749
|
+
totalCounters.p4_working_time +
|
|
750
|
+
totalCounters.p5_working_time;
|
|
751
|
+
|
|
752
|
+
const hoursSinceService =
|
|
753
|
+
serviceCounters.p1_working_time +
|
|
754
|
+
serviceCounters.p2_working_time +
|
|
755
|
+
serviceCounters.p3_working_time +
|
|
756
|
+
serviceCounters.p4_working_time +
|
|
757
|
+
serviceCounters.p5_working_time;
|
|
758
|
+
|
|
759
|
+
const powerDistribution: PowerDistributionType =
|
|
760
|
+
totalOperatingHours === 0
|
|
761
|
+
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
762
|
+
: {
|
|
763
|
+
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
764
|
+
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
765
|
+
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
766
|
+
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
767
|
+
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
return {
|
|
771
|
+
totalPowerOns: totalCounters.power_ons,
|
|
772
|
+
totalOperatingHours,
|
|
773
|
+
powerDistribution,
|
|
774
|
+
serviceStatus: {
|
|
775
|
+
totalServiceHours: deviceInfo.status.counters.service_time,
|
|
776
|
+
hoursSinceService,
|
|
777
|
+
serviceThresholdHours: serviceThreshold,
|
|
778
|
+
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
779
|
+
},
|
|
780
|
+
blackoutCount: regeneration.blackout_counter,
|
|
781
|
+
lastMaintenanceDate:
|
|
782
|
+
regeneration.last_intervention > 0
|
|
783
|
+
? new Date(regeneration.last_intervention * 1000)
|
|
784
|
+
: null,
|
|
785
|
+
alarmCount: alarmsLog.number,
|
|
786
|
+
};
|
|
787
|
+
};
|
|
788
|
+
|
|
721
789
|
const getTotalOperatingHours =
|
|
722
790
|
(baseURL: string) =>
|
|
723
791
|
/**
|
|
@@ -821,54 +889,7 @@ const getUsageAnalytics =
|
|
|
821
889
|
serviceThreshold: number = DEFAULT_SERVICE_THRESHOLD,
|
|
822
890
|
): Promise<UsageAnalyticsType> => {
|
|
823
891
|
const info = await deviceInfo(baseURL)(jwtToken, macAddress);
|
|
824
|
-
|
|
825
|
-
const totalCounters = info.nvm.total_counters;
|
|
826
|
-
const serviceCounters = info.nvm.service_counters;
|
|
827
|
-
const regeneration = info.nvm.regeneration;
|
|
828
|
-
const alarmsLog = info.nvm.alarms_log;
|
|
829
|
-
|
|
830
|
-
const totalOperatingHours =
|
|
831
|
-
totalCounters.p1_working_time +
|
|
832
|
-
totalCounters.p2_working_time +
|
|
833
|
-
totalCounters.p3_working_time +
|
|
834
|
-
totalCounters.p4_working_time +
|
|
835
|
-
totalCounters.p5_working_time;
|
|
836
|
-
|
|
837
|
-
const hoursSinceService =
|
|
838
|
-
serviceCounters.p1_working_time +
|
|
839
|
-
serviceCounters.p2_working_time +
|
|
840
|
-
serviceCounters.p3_working_time +
|
|
841
|
-
serviceCounters.p4_working_time +
|
|
842
|
-
serviceCounters.p5_working_time;
|
|
843
|
-
|
|
844
|
-
const powerDistribution: PowerDistributionType =
|
|
845
|
-
totalOperatingHours === 0
|
|
846
|
-
? { p1: 0, p2: 0, p3: 0, p4: 0, p5: 0 }
|
|
847
|
-
: {
|
|
848
|
-
p1: (totalCounters.p1_working_time / totalOperatingHours) * 100,
|
|
849
|
-
p2: (totalCounters.p2_working_time / totalOperatingHours) * 100,
|
|
850
|
-
p3: (totalCounters.p3_working_time / totalOperatingHours) * 100,
|
|
851
|
-
p4: (totalCounters.p4_working_time / totalOperatingHours) * 100,
|
|
852
|
-
p5: (totalCounters.p5_working_time / totalOperatingHours) * 100,
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
return {
|
|
856
|
-
totalPowerOns: totalCounters.power_ons,
|
|
857
|
-
totalOperatingHours,
|
|
858
|
-
powerDistribution,
|
|
859
|
-
serviceStatus: {
|
|
860
|
-
totalServiceHours: info.status.counters.service_time,
|
|
861
|
-
hoursSinceService,
|
|
862
|
-
serviceThresholdHours: serviceThreshold,
|
|
863
|
-
isServiceDue: hoursSinceService >= serviceThreshold,
|
|
864
|
-
},
|
|
865
|
-
blackoutCount: regeneration.blackout_counter,
|
|
866
|
-
lastMaintenanceDate:
|
|
867
|
-
regeneration.last_intervention > 0
|
|
868
|
-
? new Date(regeneration.last_intervention * 1000)
|
|
869
|
-
: null,
|
|
870
|
-
alarmCount: alarmsLog.number,
|
|
871
|
-
};
|
|
892
|
+
return deriveUsageAnalytics(info, serviceThreshold);
|
|
872
893
|
};
|
|
873
894
|
|
|
874
895
|
const registerDevice =
|