1 import crypto from
'crypto';
2 import path from
'path';
3 import { fileURLToPath
} from
'url';
5 import moment from
'moment';
7 import BaseError from
'../exception/BaseError';
8 import type { ChargingStationInfo
} from
'../types/ChargingStationInfo';
11 type ChargingStationTemplate
,
14 } from
'../types/ChargingStationTemplate';
15 import { ChargingProfileKindType
, RecurrencyKindType
} from
'../types/ocpp/1.6/ChargingProfile';
16 import type { OCPP16BootNotificationRequest
} from
'../types/ocpp/1.6/Requests';
17 import { BootReasonEnumType
, OCPP20BootNotificationRequest
} from
'../types/ocpp/2.0/Requests';
18 import type { ChargingProfile
, ChargingSchedulePeriod
} from
'../types/ocpp/ChargingProfile';
19 import { OCPPVersion
} from
'../types/ocpp/OCPPVersion';
20 import type { BootNotificationRequest
} from
'../types/ocpp/Requests';
21 import { WorkerProcessType
} from
'../types/Worker';
22 import Configuration from
'../utils/Configuration';
23 import Constants from
'../utils/Constants';
24 import logger from
'../utils/Logger';
25 import Utils from
'../utils/Utils';
27 const moduleName
= 'ChargingStationUtils';
29 export class ChargingStationUtils
{
30 private constructor() {
31 // This is intentional
34 public static getChargingStationId(
36 stationTemplate
: ChargingStationTemplate
38 // In case of multiple instances: add instance index to charging station id
39 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
40 const idSuffix
= stationTemplate
.nameSuffix
?? '';
41 const idStr
= '000000000' + index
.toString();
42 return stationTemplate
?.fixedName
43 ? stationTemplate
.baseName
44 : stationTemplate
.baseName
+
46 instanceIndex
.toString() +
47 idStr
.substring(idStr
.length
- 4) +
51 public static getHashId(index
: number, stationTemplate
: ChargingStationTemplate
): string {
52 const chargingStationInfo
= {
53 chargePointModel
: stationTemplate
.chargePointModel
,
54 chargePointVendor
: stationTemplate
.chargePointVendor
,
55 ...(!Utils
.isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
56 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
58 ...(!Utils
.isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
59 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
61 // FIXME?: Should a firmware version change always reference a new configuration file?
62 ...(!Utils
.isUndefined(stationTemplate
.firmwareVersion
) && {
63 firmwareVersion
: stationTemplate
.firmwareVersion
,
65 ...(!Utils
.isUndefined(stationTemplate
.iccid
) && { iccid
: stationTemplate
.iccid
}),
66 ...(!Utils
.isUndefined(stationTemplate
.imsi
) && { imsi
: stationTemplate
.imsi
}),
67 ...(!Utils
.isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
68 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
70 ...(!Utils
.isUndefined(stationTemplate
.meterType
) && {
71 meterType
: stationTemplate
.meterType
,
75 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
77 JSON
.stringify(chargingStationInfo
) +
78 ChargingStationUtils
.getChargingStationId(index
, stationTemplate
)
83 public static getTemplateMaxNumberOfConnectors(stationTemplate
: ChargingStationTemplate
): number {
84 const templateConnectors
= stationTemplate
?.Connectors
;
85 if (!templateConnectors
) {
88 return Object.keys(templateConnectors
).length
;
91 public static checkTemplateMaxConnectors(
92 templateMaxConnectors
: number,
96 if (templateMaxConnectors
=== 0) {
98 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
100 } else if (templateMaxConnectors
< 0) {
102 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
107 public static getConfiguredNumberOfConnectors(stationTemplate
: ChargingStationTemplate
): number {
108 let configuredMaxConnectors
: number;
109 if (Utils
.isEmptyArray(stationTemplate
.numberOfConnectors
) === false) {
110 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
111 configuredMaxConnectors
=
112 numberOfConnectors
[Math.floor(Utils
.secureRandom() * numberOfConnectors
.length
)];
113 } else if (Utils
.isUndefined(stationTemplate
.numberOfConnectors
) === false) {
114 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
116 configuredMaxConnectors
= stationTemplate
?.Connectors
[0]
117 ? ChargingStationUtils
.getTemplateMaxNumberOfConnectors(stationTemplate
) - 1
118 : ChargingStationUtils
.getTemplateMaxNumberOfConnectors(stationTemplate
);
120 return configuredMaxConnectors
;
123 public static checkConfiguredMaxConnectors(
124 configuredMaxConnectors
: number,
125 templateFile
: string,
128 if (configuredMaxConnectors
<= 0) {
130 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
135 public static createBootNotificationRequest(
136 stationInfo
: ChargingStationInfo
137 ): BootNotificationRequest
{
138 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
139 switch (ocppVersion
) {
140 case OCPPVersion
.VERSION_16
:
142 chargePointModel
: stationInfo
.chargePointModel
,
143 chargePointVendor
: stationInfo
.chargePointVendor
,
144 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
145 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
147 ...(!Utils
.isUndefined(stationInfo
.chargePointSerialNumber
) && {
148 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
150 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
151 firmwareVersion
: stationInfo
.firmwareVersion
,
153 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
154 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
155 ...(!Utils
.isUndefined(stationInfo
.meterSerialNumber
) && {
156 meterSerialNumber
: stationInfo
.meterSerialNumber
,
158 ...(!Utils
.isUndefined(stationInfo
.meterType
) && {
159 meterType
: stationInfo
.meterType
,
161 } as OCPP16BootNotificationRequest
;
162 case OCPPVersion
.VERSION_20
:
163 case OCPPVersion
.VERSION_201
:
165 reason
: BootReasonEnumType
.PowerUp
,
167 model
: stationInfo
.chargePointModel
,
168 vendorName
: stationInfo
.chargePointVendor
,
169 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
170 firmwareVersion
: stationInfo
.firmwareVersion
,
172 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
173 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
175 ...((!Utils
.isUndefined(stationInfo
.iccid
) || !Utils
.isUndefined(stationInfo
.imsi
)) && {
177 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
178 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
182 } as OCPP20BootNotificationRequest
;
186 public static workerPoolInUse(): boolean {
187 return [WorkerProcessType
.DYNAMIC_POOL
, WorkerProcessType
.STATIC_POOL
].includes(
188 Configuration
.getWorker().processType
192 public static workerDynamicPoolInUse(): boolean {
193 return Configuration
.getWorker().processType
=== WorkerProcessType
.DYNAMIC_POOL
;
196 public static warnDeprecatedTemplateKey(
197 template
: ChargingStationTemplate
,
199 templateFile
: string,
203 if (!Utils
.isUndefined(template
[key
])) {
205 `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
206 logMsgToAppend && '. ' + logMsgToAppend
212 public static convertDeprecatedTemplateKey(
213 template
: ChargingStationTemplate
,
214 deprecatedKey
: string,
217 if (!Utils
.isUndefined(template
[deprecatedKey
])) {
218 template
[key
] = template
[deprecatedKey
] as unknown
;
219 delete template
[deprecatedKey
];
223 public static stationTemplateToStationInfo(
224 stationTemplate
: ChargingStationTemplate
225 ): ChargingStationInfo
{
226 stationTemplate
= Utils
.cloneObject(stationTemplate
);
227 delete stationTemplate
.power
;
228 delete stationTemplate
.powerUnit
;
229 delete stationTemplate
.Configuration
;
230 delete stationTemplate
.AutomaticTransactionGenerator
;
231 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
232 delete stationTemplate
.chargePointSerialNumberPrefix
;
233 delete stationTemplate
.meterSerialNumberPrefix
;
234 return stationTemplate
as unknown
as ChargingStationInfo
;
237 public static createStationInfoHash(stationInfo
: ChargingStationInfo
): void {
238 delete stationInfo
.infoHash
;
239 stationInfo
.infoHash
= crypto
240 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
241 .update(JSON
.stringify(stationInfo
))
245 public static createSerialNumber(
246 stationTemplate
: ChargingStationTemplate
,
247 stationInfo
: ChargingStationInfo
= {} as ChargingStationInfo
,
249 randomSerialNumberUpperCase
?: boolean;
250 randomSerialNumber
?: boolean;
252 randomSerialNumberUpperCase
: true,
253 randomSerialNumber
: true,
256 params
= params
?? {};
257 params
.randomSerialNumberUpperCase
= params
?.randomSerialNumberUpperCase
?? true;
258 params
.randomSerialNumber
= params
?.randomSerialNumber
?? true;
259 const serialNumberSuffix
= params
?.randomSerialNumber
260 ? ChargingStationUtils
.getRandomSerialNumberSuffix({
261 upperCase
: params
.randomSerialNumberUpperCase
,
264 stationInfo
.chargePointSerialNumber
=
265 stationTemplate
?.chargePointSerialNumberPrefix
&&
266 stationTemplate
.chargePointSerialNumberPrefix
+ serialNumberSuffix
;
267 stationInfo
.chargeBoxSerialNumber
=
268 stationTemplate
?.chargeBoxSerialNumberPrefix
&&
269 stationTemplate
.chargeBoxSerialNumberPrefix
+ serialNumberSuffix
;
270 stationInfo
.meterSerialNumber
=
271 stationTemplate
?.meterSerialNumberPrefix
&&
272 stationTemplate
.meterSerialNumberPrefix
+ serialNumberSuffix
;
275 public static propagateSerialNumber(
276 stationTemplate
: ChargingStationTemplate
,
277 stationInfoSrc
: ChargingStationInfo
,
278 stationInfoDst
: ChargingStationInfo
= {} as ChargingStationInfo
280 if (!stationInfoSrc
|| !stationTemplate
) {
282 'Missing charging station template or existing configuration to propagate serial number'
285 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
286 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
287 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
288 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
289 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
290 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
291 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
292 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
293 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
296 public static getAmperageLimitationUnitDivider(stationInfo
: ChargingStationInfo
): number {
298 switch (stationInfo
.amperageLimitationUnit
) {
299 case AmpereUnits
.DECI_AMPERE
:
302 case AmpereUnits
.CENTI_AMPERE
:
305 case AmpereUnits
.MILLI_AMPERE
:
313 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
315 * @param chargingProfiles -
319 public static getLimitFromChargingProfiles(
320 chargingProfiles
: ChargingProfile
[],
324 matchingChargingProfile
: ChargingProfile
;
326 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
327 for (const chargingProfile
of chargingProfiles
) {
329 const currentMoment
= moment();
330 const chargingSchedule
= chargingProfile
.chargingSchedule
;
331 // Check type (recurring) and if it is already active
332 // Adjust the daily recurring schedule to today
334 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
335 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
&&
336 currentMoment
.isAfter(chargingSchedule
.startSchedule
)
338 const currentDate
= new Date();
339 chargingSchedule
.startSchedule
= new Date(chargingSchedule
.startSchedule
);
340 chargingSchedule
.startSchedule
.setFullYear(
341 currentDate
.getFullYear(),
342 currentDate
.getMonth(),
343 currentDate
.getDate()
345 // Check if the start of the schedule is yesterday
346 if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
347 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
349 } else if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
352 // Check if the charging profile is active
354 moment(chargingSchedule
.startSchedule
)
355 .add(chargingSchedule
.duration
, 's')
356 .isAfter(currentMoment
)
358 let lastButOneSchedule
: ChargingSchedulePeriod
;
359 // Search the right schedule period
360 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
361 // Handling of only one period
363 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
364 schedulePeriod
.startPeriod
=== 0
367 limit
: schedulePeriod
.limit
,
368 matchingChargingProfile
: chargingProfile
,
370 logger
.debug(debugLogMsg
, result
);
373 // Find the right schedule period
375 moment(chargingSchedule
.startSchedule
)
376 .add(schedulePeriod
.startPeriod
, 's')
377 .isAfter(currentMoment
)
379 // Found the schedule: last but one is the correct one
381 limit
: lastButOneSchedule
.limit
,
382 matchingChargingProfile
: chargingProfile
,
384 logger
.debug(debugLogMsg
, result
);
388 lastButOneSchedule
= schedulePeriod
;
389 // Handle the last schedule period
391 schedulePeriod
.startPeriod
===
392 chargingSchedule
.chargingSchedulePeriod
[
393 chargingSchedule
.chargingSchedulePeriod
.length
- 1
397 limit
: lastButOneSchedule
.limit
,
398 matchingChargingProfile
: chargingProfile
,
400 logger
.debug(debugLogMsg
, result
);
409 public static getDefaultVoltageOut(
410 currentType
: CurrentType
,
411 templateFile
: string,
414 const errMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
415 let defaultVoltageOut
: number;
416 switch (currentType
) {
418 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
421 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
424 logger
.error(`${logPrefix} ${errMsg}`);
425 throw new BaseError(errMsg
);
427 return defaultVoltageOut
;
430 public static getAuthorizationFile(stationInfo
: ChargingStationInfo
): string | undefined {
432 stationInfo
.authorizationFile
&&
434 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
436 path
.basename(stationInfo
.authorizationFile
)
441 private static getRandomSerialNumberSuffix(params
?: {
442 randomBytesLength
?: number;
445 const randomSerialNumberSuffix
= crypto
446 .randomBytes(params
?.randomBytesLength
?? 16)
448 if (params
?.upperCase
) {
449 return randomSerialNumberSuffix
.toUpperCase();
451 return randomSerialNumberSuffix
;