1 import { ChargingProfile
, ChargingSchedulePeriod
} from
'../types/ocpp/ChargingProfile';
2 import { ChargingProfileKindType
, RecurrencyKindType
} from
'../types/ocpp/1.6/ChargingProfile';
3 import ChargingStationTemplate
, { AmpereUnits
} from
'../types/ChargingStationTemplate';
5 import { BootNotificationRequest
} from
'../types/ocpp/Requests';
6 import ChargingStationInfo from
'../types/ChargingStationInfo';
7 import Configuration from
'../utils/Configuration';
8 import Constants from
'../utils/Constants';
9 import Utils from
'../utils/Utils';
10 import { WebSocketCloseEventStatusString
} from
'../types/WebSocket';
11 import { WorkerProcessType
} from
'../types/Worker';
12 import crypto from
'crypto';
13 import logger from
'../utils/Logger';
14 import moment from
'moment';
16 export class ChargingStationUtils
{
17 public static getChargingStationId(
19 stationTemplate
: ChargingStationTemplate
21 // In case of multiple instances: add instance index to charging station id
22 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
23 const idSuffix
= stationTemplate
.nameSuffix
?? '';
24 const idStr
= '000000000' + index
.toString();
25 return stationTemplate
.fixedName
26 ? stationTemplate
.baseName
27 : stationTemplate
.baseName
+
29 instanceIndex
.toString() +
30 idStr
.substring(idStr
.length
- 4) +
34 public static getHashId(stationInfo
: ChargingStationInfo
): string {
35 const hashBootNotificationRequest
= {
36 chargePointModel
: stationInfo
.chargePointModel
,
37 chargePointVendor
: stationInfo
.chargePointVendor
,
38 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumberPrefix
) && {
39 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumberPrefix
,
41 ...(!Utils
.isUndefined(stationInfo
.chargePointSerialNumberPrefix
) && {
42 chargePointSerialNumber
: stationInfo
.chargePointSerialNumberPrefix
,
44 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
45 firmwareVersion
: stationInfo
.firmwareVersion
,
47 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
48 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
49 ...(!Utils
.isUndefined(stationInfo
.meterSerialNumberPrefix
) && {
50 meterSerialNumber
: stationInfo
.meterSerialNumberPrefix
,
52 ...(!Utils
.isUndefined(stationInfo
.meterType
) && {
53 meterType
: stationInfo
.meterType
,
57 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
58 .update(JSON
.stringify(hashBootNotificationRequest
) + stationInfo
.chargingStationId
)
62 public static createBootNotificationRequest(
63 stationInfo
: ChargingStationInfo
64 ): BootNotificationRequest
{
66 chargePointModel
: stationInfo
.chargePointModel
,
67 chargePointVendor
: stationInfo
.chargePointVendor
,
68 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
69 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
71 ...(!Utils
.isUndefined(stationInfo
.chargePointSerialNumber
) && {
72 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
74 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
75 firmwareVersion
: stationInfo
.firmwareVersion
,
77 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
78 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
79 ...(!Utils
.isUndefined(stationInfo
.meterSerialNumber
) && {
80 meterSerialNumber
: stationInfo
.meterSerialNumber
,
82 ...(!Utils
.isUndefined(stationInfo
.meterType
) && {
83 meterType
: stationInfo
.meterType
,
88 public static workerPoolInUse(): boolean {
89 return [WorkerProcessType
.DYNAMIC_POOL
, WorkerProcessType
.STATIC_POOL
].includes(
90 Configuration
.getWorkerProcess()
94 public static workerDynamicPoolInUse(): boolean {
95 return Configuration
.getWorkerProcess() === WorkerProcessType
.DYNAMIC_POOL
;
99 * Convert websocket error code to human readable string message
101 * @param code websocket error code
102 * @returns human readable string message
104 public static getWebSocketCloseEventStatusString(code
: number): string {
105 if (code
>= 0 && code
<= 999) {
107 } else if (code
>= 1016) {
109 return '(For WebSocket standard)';
110 } else if (code
<= 2999) {
111 return '(For WebSocket extensions)';
112 } else if (code
<= 3999) {
113 return '(For libraries and frameworks)';
114 } else if (code
<= 4999) {
115 return '(For applications)';
118 if (!Utils
.isUndefined(WebSocketCloseEventStatusString
[code
])) {
119 return WebSocketCloseEventStatusString
[code
] as string;
124 public static warnDeprecatedTemplateKey(
125 template
: ChargingStationTemplate
,
127 templateFile
: string,
131 if (!Utils
.isUndefined(template
[key
])) {
133 `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
134 logMsgToAppend && '. ' + logMsgToAppend
140 public static convertDeprecatedTemplateKey(
141 template
: ChargingStationTemplate
,
142 deprecatedKey
: string,
145 if (!Utils
.isUndefined(template
[deprecatedKey
])) {
146 template
[key
] = template
[deprecatedKey
] as unknown
;
147 delete template
[deprecatedKey
];
151 public static createStationInfoHash(stationInfo
: ChargingStationInfo
): ChargingStationInfo
{
152 if (!Utils
.isEmptyObject(stationInfo
)) {
153 const previousInfoHash
= stationInfo
?.infoHash
?? '';
154 delete stationInfo
.infoHash
;
155 const currentInfoHash
= crypto
156 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
157 .update(JSON
.stringify(stationInfo
))
160 Utils
.isEmptyString(previousInfoHash
) ||
161 (!Utils
.isEmptyString(previousInfoHash
) && currentInfoHash
!== previousInfoHash
)
163 stationInfo
.infoHash
= currentInfoHash
;
165 stationInfo
.infoHash
= previousInfoHash
;
171 public static createSerialNumber(
172 stationInfo
: ChargingStationInfo
,
173 existingStationInfo
?: ChargingStationInfo
,
174 params
: { randomSerialNumberUpperCase
?: boolean; randomSerialNumber
?: boolean } = {
175 randomSerialNumberUpperCase
: true,
176 randomSerialNumber
: true,
179 params
= params
?? {};
180 params
.randomSerialNumberUpperCase
= params
?.randomSerialNumberUpperCase
?? true;
181 params
.randomSerialNumber
= params
?.randomSerialNumber
?? true;
182 if (!Utils
.isEmptyObject(existingStationInfo
)) {
183 existingStationInfo
?.chargePointSerialNumber
&&
184 (stationInfo
.chargePointSerialNumber
= existingStationInfo
.chargePointSerialNumber
);
185 existingStationInfo
?.chargeBoxSerialNumber
&&
186 (stationInfo
.chargeBoxSerialNumber
= existingStationInfo
.chargeBoxSerialNumber
);
187 existingStationInfo
?.meterSerialNumber
&&
188 (stationInfo
.meterSerialNumber
= existingStationInfo
.meterSerialNumber
);
190 const serialNumberSuffix
= params
?.randomSerialNumber
191 ? ChargingStationUtils
.getRandomSerialNumberSuffix({
192 upperCase
: params
.randomSerialNumberUpperCase
,
195 stationInfo
.chargePointSerialNumber
=
196 stationInfo
?.chargePointSerialNumberPrefix
&&
197 stationInfo
.chargePointSerialNumberPrefix
+ serialNumberSuffix
;
198 stationInfo
.chargeBoxSerialNumber
=
199 stationInfo
?.chargeBoxSerialNumberPrefix
&&
200 stationInfo
.chargeBoxSerialNumberPrefix
+ serialNumberSuffix
;
201 stationInfo
.meterSerialNumber
=
202 stationInfo
?.meterSerialNumberPrefix
&&
203 stationInfo
.meterSerialNumberPrefix
+ serialNumberSuffix
;
207 public static getAmperageLimitationUnitDivider(stationInfo
: ChargingStationInfo
): number {
209 switch (stationInfo
.amperageLimitationUnit
) {
210 case AmpereUnits
.DECI_AMPERE
:
213 case AmpereUnits
.CENTI_AMPERE
:
216 case AmpereUnits
.MILLI_AMPERE
:
224 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
226 * @param {ChargingProfile[]} chargingProfiles
227 * @param {string} logPrefix
228 * @returns {{ limit, matchingChargingProfile }}
230 public static getLimitFromChargingProfiles(
231 chargingProfiles
: ChargingProfile
[],
235 matchingChargingProfile
: ChargingProfile
;
237 for (const chargingProfile
of chargingProfiles
) {
239 const currentMoment
= moment();
240 const chargingSchedule
= chargingProfile
.chargingSchedule
;
241 // Check type (recurring) and if it is already active
242 // Adjust the daily recurring schedule to today
244 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
245 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
&&
246 currentMoment
.isAfter(chargingSchedule
.startSchedule
)
248 const currentDate
= new Date();
249 chargingSchedule
.startSchedule
= new Date(chargingSchedule
.startSchedule
);
250 chargingSchedule
.startSchedule
.setFullYear(
251 currentDate
.getFullYear(),
252 currentDate
.getMonth(),
253 currentDate
.getDate()
255 // Check if the start of the schedule is yesterday
256 if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
257 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
259 } else if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
262 // Check if the charging profile is active
264 moment(chargingSchedule
.startSchedule
)
265 .add(chargingSchedule
.duration
, 's')
266 .isAfter(currentMoment
)
268 let lastButOneSchedule
: ChargingSchedulePeriod
;
269 // Search the right schedule period
270 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
271 // Handling of only one period
273 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
274 schedulePeriod
.startPeriod
=== 0
277 limit
: schedulePeriod
.limit
,
278 matchingChargingProfile
: chargingProfile
,
281 `${logPrefix} Matching charging profile found for power limitation: %j`,
286 // Find the right schedule period
288 moment(chargingSchedule
.startSchedule
)
289 .add(schedulePeriod
.startPeriod
, 's')
290 .isAfter(currentMoment
)
292 // Found the schedule: last but one is the correct one
294 limit
: lastButOneSchedule
.limit
,
295 matchingChargingProfile
: chargingProfile
,
298 `${logPrefix} Matching charging profile found for power limitation: %j`,
304 lastButOneSchedule
= schedulePeriod
;
305 // Handle the last schedule period
307 schedulePeriod
.startPeriod
===
308 chargingSchedule
.chargingSchedulePeriod
[
309 chargingSchedule
.chargingSchedulePeriod
.length
- 1
313 limit
: lastButOneSchedule
.limit
,
314 matchingChargingProfile
: chargingProfile
,
317 `${logPrefix} Matching charging profile found for power limitation: %j`,
328 private static getRandomSerialNumberSuffix(params
?: {
329 randomBytesLength
?: number;
332 const randomSerialNumberSuffix
= crypto
333 .randomBytes(params
?.randomBytesLength
?? 16)
335 if (params
?.upperCase
) {
336 return randomSerialNumberSuffix
.toUpperCase();
338 return randomSerialNumberSuffix
;