feat: add connector status handling at boot with evses configuration
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
CommitLineData
43ee4373 1import crypto from 'node:crypto';
130783a7
JB
2import path from 'node:path';
3import { fileURLToPath } from 'node:url';
8114d10e 4
e302df1d 5import chalk from 'chalk';
8114d10e
JB
6import moment from 'moment';
7
2896e06d 8import type { ChargingStation } from './internal';
268a74bb 9import { BaseError } from '../exception';
981ebfbe 10import {
492cf6ab 11 AmpereUnits,
04b1261c 12 AvailabilityType,
268a74bb
JB
13 type BootNotificationRequest,
14 BootReasonEnumType,
15068be9 15 type ChargingProfile,
268a74bb 16 ChargingProfileKindType,
15068be9
JB
17 ChargingRateUnitType,
18 type ChargingSchedulePeriod,
268a74bb
JB
19 type ChargingStationInfo,
20 type ChargingStationTemplate,
a78ef5ed 21 type ConnectorStatus,
c3b83130 22 ConnectorStatusEnum,
268a74bb 23 CurrentType,
ae25f265 24 type EvseTemplate,
268a74bb
JB
25 type OCPP16BootNotificationRequest,
26 type OCPP20BootNotificationRequest,
27 OCPPVersion,
28 RecurrencyKindType,
29 Voltage,
30} from '../types';
60a74391
JB
31import {
32 ACElectricUtils,
33 Configuration,
34 Constants,
35 DCElectricUtils,
36 Utils,
37 logger,
38} from '../utils';
268a74bb 39import { WorkerProcessType } from '../worker';
17ac262c 40
91a4f151
JB
41const moduleName = 'ChargingStationUtils';
42
17ac262c 43export class ChargingStationUtils {
d5bd1c00
JB
44 private constructor() {
45 // This is intentional
46 }
47
17ac262c
JB
48 public static getChargingStationId(
49 index: number,
50 stationTemplate: ChargingStationTemplate
51 ): string {
52 // In case of multiple instances: add instance index to charging station id
53 const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
1b271a54 54 const idSuffix = stationTemplate?.nameSuffix ?? '';
44eb6026 55 const idStr = `000000000${index.toString()}`;
ccb1d6e9 56 return stationTemplate?.fixedName
17ac262c 57 ? stationTemplate.baseName
44eb6026
JB
58 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
59 idStr.length - 4
60 )}${idSuffix}`;
17ac262c
JB
61 }
62
fa7bccf4 63 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
99e92237 64 const chargingStationInfo = {
fa7bccf4
JB
65 chargePointModel: stationTemplate.chargePointModel,
66 chargePointVendor: stationTemplate.chargePointVendor,
67 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
68 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
17ac262c 69 }),
fa7bccf4
JB
70 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
71 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
17ac262c 72 }),
fa7bccf4
JB
73 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
74 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
17ac262c 75 }),
fa7bccf4
JB
76 ...(!Utils.isUndefined(stationTemplate.meterType) && {
77 meterType: stationTemplate.meterType,
17ac262c
JB
78 }),
79 };
80 return crypto
81 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
fa7bccf4 82 .update(
14ecae6a
JB
83 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
84 index,
85 stationTemplate
86 )}`
fa7bccf4 87 )
17ac262c
JB
88 .digest('hex');
89 }
90
1bf29f5b
JB
91 public static checkChargingStation(chargingStation: ChargingStation, logPrefix: string): boolean {
92 if (chargingStation.started === false && chargingStation.starting === false) {
93 logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
94 return false;
95 }
96 return true;
97 }
98
ae25f265
JB
99 public static getMaxNumberOfEvses(evses: Record<string, EvseTemplate>): number {
100 if (!evses) {
101 return -1;
102 }
103 return Object.keys(evses).length;
104 }
105
a78ef5ed
JB
106 public static getMaxNumberOfConnectors(connectors: Record<string, ConnectorStatus>): number {
107 if (!connectors) {
fa7bccf4
JB
108 return -1;
109 }
a78ef5ed 110 return Object.keys(connectors).length;
fa7bccf4
JB
111 }
112
113 public static checkTemplateMaxConnectors(
114 templateMaxConnectors: number,
115 templateFile: string,
116 logPrefix: string
117 ): void {
118 if (templateMaxConnectors === 0) {
119 logger.warn(
120 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
121 );
122 } else if (templateMaxConnectors < 0) {
123 logger.error(
124 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
125 );
126 }
127 }
128
c3b83130
JB
129 public static getBootConnectorStatus(
130 chargingStation: ChargingStation,
131 connectorId: number,
132 connectorStatus: ConnectorStatus
133 ): ConnectorStatusEnum {
134 let connectorBootStatus: ConnectorStatusEnum;
135 if (
136 !connectorStatus?.status &&
137 (chargingStation.isChargingStationAvailable() === false ||
138 chargingStation.isConnectorAvailable(connectorId) === false)
139 ) {
140 connectorBootStatus = ConnectorStatusEnum.Unavailable;
141 } else if (!connectorStatus?.status && connectorStatus?.bootStatus) {
142 // Set boot status in template at startup
143 connectorBootStatus = connectorStatus?.bootStatus;
144 } else if (connectorStatus?.status) {
145 // Set previous status at startup
146 connectorBootStatus = connectorStatus?.status;
147 } else {
148 // Set default status
149 connectorBootStatus = ConnectorStatusEnum.Available;
150 }
151 return connectorBootStatus;
152 }
153
ae25f265 154 public static getConfiguredNumberOfConnectors(stationInfo: ChargingStationInfo): number {
fa7bccf4 155 let configuredMaxConnectors: number;
ae25f265
JB
156 if (Utils.isNotEmptyArray(stationInfo.numberOfConnectors) === true) {
157 const numberOfConnectors = stationInfo.numberOfConnectors as number[];
c72f6634
JB
158 configuredMaxConnectors =
159 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
ae25f265
JB
160 } else if (Utils.isUndefined(stationInfo.numberOfConnectors) === false) {
161 configuredMaxConnectors = stationInfo.numberOfConnectors as number;
162 } else if (stationInfo.Connectors && !stationInfo.Evses) {
163 configuredMaxConnectors = stationInfo?.Connectors[0]
164 ? ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors) - 1
165 : ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors);
166 } else if (stationInfo.Evses && !stationInfo.Connectors) {
a78ef5ed 167 configuredMaxConnectors = 0;
ae25f265 168 for (const evse in stationInfo.Evses) {
a78ef5ed
JB
169 if (evse === '0') {
170 continue;
171 }
172 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
ae25f265 173 stationInfo.Evses[evse].Connectors
a78ef5ed
JB
174 );
175 }
fa7bccf4
JB
176 }
177 return configuredMaxConnectors;
178 }
179
180 public static checkConfiguredMaxConnectors(
181 configuredMaxConnectors: number,
182 templateFile: string,
183 logPrefix: string
184 ): void {
185 if (configuredMaxConnectors <= 0) {
186 logger.warn(
187 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
188 );
189 }
190 }
191
04b1261c
JB
192 public static checkStationInfoConnectorStatus(
193 connectorId: number,
194 connectorStatus: ConnectorStatus,
195 logPrefix: string,
196 templateFile: string
197 ): void {
198 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
199 logger.warn(
54ebb82c 200 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
04b1261c
JB
201 );
202 delete connectorStatus.status;
203 }
204 }
205
206 public static buildConnectorsMap(
207 connectors: Record<string, ConnectorStatus>,
208 logPrefix: string,
209 templateFile: string
210 ): Map<number, ConnectorStatus> {
211 const connectorsMap = new Map<number, ConnectorStatus>();
ae25f265
JB
212 if (ChargingStationUtils.getMaxNumberOfConnectors(connectors) > 0) {
213 for (const connector in connectors) {
214 const connectorStatus = connectors[connector];
215 const connectorId = Utils.convertToInt(connector);
216 ChargingStationUtils.checkStationInfoConnectorStatus(
217 connectorId,
218 connectorStatus,
219 logPrefix,
220 templateFile
221 );
222 connectorsMap.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
04b1261c 223 }
ae25f265
JB
224 } else {
225 logger.warn(
226 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
227 );
04b1261c
JB
228 }
229 return connectorsMap;
230 }
231
232 public static initializeConnectorsMapStatus(
233 connectors: Map<number, ConnectorStatus>,
234 logPrefix: string
235 ): void {
236 for (const connectorId of connectors.keys()) {
237 if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
238 logger.warn(
54ebb82c 239 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
04b1261c
JB
240 connectors.get(connectorId)?.transactionId
241 }`
242 );
243 }
8e2430ee 244 if (connectorId === 0) {
52952bf8
JB
245 connectors.get(connectorId).availability = AvailabilityType.Operative;
246 if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
247 connectors.get(connectorId).chargingProfiles = [];
248 }
249 } else if (
04b1261c
JB
250 connectorId > 0 &&
251 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
252 ) {
253 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
254 }
255 }
256 }
257
04b1261c
JB
258 public static resetConnectorStatus(connectorStatus: ConnectorStatus): void {
259 connectorStatus.idTagLocalAuthorized = false;
260 connectorStatus.idTagAuthorized = false;
261 connectorStatus.transactionRemoteStarted = false;
262 connectorStatus.transactionStarted = false;
263 delete connectorStatus?.localAuthorizeIdTag;
264 delete connectorStatus?.authorizeIdTag;
265 delete connectorStatus?.transactionId;
266 delete connectorStatus?.transactionIdTag;
267 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
268 delete connectorStatus?.transactionBeginMeterValue;
269 }
270
17ac262c 271 public static createBootNotificationRequest(
15068be9
JB
272 stationInfo: ChargingStationInfo,
273 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
17ac262c 274 ): BootNotificationRequest {
d270cc87
JB
275 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
276 switch (ocppVersion) {
277 case OCPPVersion.VERSION_16:
278 return {
279 chargePointModel: stationInfo.chargePointModel,
280 chargePointVendor: stationInfo.chargePointVendor,
281 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
282 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
283 }),
284 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
285 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
286 }),
287 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
288 firmwareVersion: stationInfo.firmwareVersion,
289 }),
290 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
291 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
292 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
293 meterSerialNumber: stationInfo.meterSerialNumber,
294 }),
295 ...(!Utils.isUndefined(stationInfo.meterType) && {
296 meterType: stationInfo.meterType,
297 }),
298 } as OCPP16BootNotificationRequest;
299 case OCPPVersion.VERSION_20:
300 case OCPPVersion.VERSION_201:
301 return {
15068be9 302 reason: bootReason,
d270cc87
JB
303 chargingStation: {
304 model: stationInfo.chargePointModel,
305 vendorName: stationInfo.chargePointVendor,
306 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
307 firmwareVersion: stationInfo.firmwareVersion,
308 }),
309 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
310 serialNumber: stationInfo.chargeBoxSerialNumber,
311 }),
98fc1389
JB
312 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
313 modem: {
314 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
315 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
316 },
317 }),
d270cc87
JB
318 },
319 } as OCPP20BootNotificationRequest;
320 }
17ac262c
JB
321 }
322
323 public static workerPoolInUse(): boolean {
721646e9 324 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
cf2a5d9b 325 Configuration.getWorker().processType
17ac262c
JB
326 );
327 }
328
329 public static workerDynamicPoolInUse(): boolean {
721646e9 330 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
17ac262c
JB
331 }
332
ae5020a3 333 public static warnTemplateKeysDeprecation(
17ac262c 334 templateFile: string,
ae5020a3
JB
335 stationTemplate: ChargingStationTemplate,
336 logPrefix: string
337 ) {
338 const templateKeys: { key: string; deprecatedKey: string }[] = [
339 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
340 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
341 ];
342 for (const templateKey of templateKeys) {
343 ChargingStationUtils.warnDeprecatedTemplateKey(
344 stationTemplate,
345 templateKey.deprecatedKey,
346 templateFile,
347 logPrefix,
348 `Use '${templateKey.key}' instead`
349 );
350 ChargingStationUtils.convertDeprecatedTemplateKey(
351 stationTemplate,
352 templateKey.deprecatedKey,
353 templateKey.key
354 );
17ac262c
JB
355 }
356 }
357
fa7bccf4
JB
358 public static stationTemplateToStationInfo(
359 stationTemplate: ChargingStationTemplate
360 ): ChargingStationInfo {
361 stationTemplate = Utils.cloneObject(stationTemplate);
362 delete stationTemplate.power;
363 delete stationTemplate.powerUnit;
364 delete stationTemplate.Configuration;
365 delete stationTemplate.AutomaticTransactionGenerator;
366 delete stationTemplate.chargeBoxSerialNumberPrefix;
367 delete stationTemplate.chargePointSerialNumberPrefix;
fec4d204 368 delete stationTemplate.meterSerialNumberPrefix;
51c83d6f 369 return stationTemplate as unknown as ChargingStationInfo;
fa7bccf4
JB
370 }
371
372 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
ccb1d6e9 373 delete stationInfo.infoHash;
7c72977b 374 stationInfo.infoHash = crypto
ccb1d6e9
JB
375 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
376 .update(JSON.stringify(stationInfo))
377 .digest('hex');
17ac262c
JB
378 }
379
380 public static createSerialNumber(
fa7bccf4 381 stationTemplate: ChargingStationTemplate,
4d20f040 382 stationInfo: ChargingStationInfo,
fa7bccf4
JB
383 params: {
384 randomSerialNumberUpperCase?: boolean;
385 randomSerialNumber?: boolean;
386 } = {
17ac262c
JB
387 randomSerialNumberUpperCase: true,
388 randomSerialNumber: true,
389 }
390 ): void {
abe9e9dd 391 params = params ?? {};
17ac262c
JB
392 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
393 params.randomSerialNumber = params?.randomSerialNumber ?? true;
fa7bccf4
JB
394 const serialNumberSuffix = params?.randomSerialNumber
395 ? ChargingStationUtils.getRandomSerialNumberSuffix({
396 upperCase: params.randomSerialNumberUpperCase,
397 })
398 : '';
9a15316c
JB
399 stationInfo.chargePointSerialNumber = Utils.isNotEmptyString(
400 stationTemplate?.chargePointSerialNumberPrefix
401 )
402 ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`
403 : undefined;
404 stationInfo.chargeBoxSerialNumber = Utils.isNotEmptyString(
405 stationTemplate?.chargeBoxSerialNumberPrefix
406 )
407 ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`
408 : undefined;
409 stationInfo.meterSerialNumber = Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix)
410 ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`
411 : undefined;
fec4d204
JB
412 }
413
414 public static propagateSerialNumber(
415 stationTemplate: ChargingStationTemplate,
416 stationInfoSrc: ChargingStationInfo,
4d20f040 417 stationInfoDst: ChargingStationInfo
fec4d204
JB
418 ) {
419 if (!stationInfoSrc || !stationTemplate) {
baf93dda
JB
420 throw new BaseError(
421 'Missing charging station template or existing configuration to propagate serial number'
422 );
fec4d204
JB
423 }
424 stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
425 ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
426 : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber;
427 stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber
428 ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
429 : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber;
430 stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber
431 ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
432 : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber;
17ac262c
JB
433 }
434
435 public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
436 let unitDivider = 1;
437 switch (stationInfo.amperageLimitationUnit) {
438 case AmpereUnits.DECI_AMPERE:
439 unitDivider = 10;
440 break;
441 case AmpereUnits.CENTI_AMPERE:
442 unitDivider = 100;
443 break;
444 case AmpereUnits.MILLI_AMPERE:
445 unitDivider = 1000;
446 break;
447 }
448 return unitDivider;
449 }
450
15068be9
JB
451 public static getChargingStationConnectorChargingProfilesPowerLimit(
452 chargingStation: ChargingStation,
453 connectorId: number
454 ): number | undefined {
455 let limit: number, matchingChargingProfile: ChargingProfile;
15068be9 456 // Get charging profiles for connector and sort by stack level
66a62eac
JB
457 const chargingProfiles =
458 Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort(
459 (a, b) => b.stackLevel - a.stackLevel
460 ) ?? [];
15068be9 461 // Get profiles on connector 0
1895299d 462 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
15068be9 463 chargingProfiles.push(
66a62eac
JB
464 ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort(
465 (a, b) => b.stackLevel - a.stackLevel
466 )
15068be9
JB
467 );
468 }
53ac516c 469 if (Utils.isNotEmptyArray(chargingProfiles)) {
15068be9
JB
470 const result = ChargingStationUtils.getLimitFromChargingProfiles(
471 chargingProfiles,
472 chargingStation.logPrefix()
473 );
474 if (!Utils.isNullOrUndefined(result)) {
1895299d
JB
475 limit = result?.limit;
476 matchingChargingProfile = result?.matchingChargingProfile;
15068be9
JB
477 switch (chargingStation.getCurrentOutType()) {
478 case CurrentType.AC:
479 limit =
480 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
481 ChargingRateUnitType.WATT
482 ? limit
483 : ACElectricUtils.powerTotal(
484 chargingStation.getNumberOfPhases(),
485 chargingStation.getVoltageOut(),
486 limit
487 );
488 break;
489 case CurrentType.DC:
490 limit =
491 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
492 ChargingRateUnitType.WATT
493 ? limit
494 : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
495 }
496 const connectorMaximumPower =
497 chargingStation.getMaximumPower() / chargingStation.powerDivider;
498 if (limit > connectorMaximumPower) {
499 logger.error(
500 `${chargingStation.logPrefix()} Charging profile id ${
501 matchingChargingProfile.chargingProfileId
502 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
503 result
504 );
505 limit = connectorMaximumPower;
506 }
507 }
508 }
509 return limit;
510 }
511
512 public static getDefaultVoltageOut(
513 currentType: CurrentType,
514 templateFile: string,
515 logPrefix: string
516 ): Voltage {
517 const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
518 let defaultVoltageOut: number;
519 switch (currentType) {
520 case CurrentType.AC:
521 defaultVoltageOut = Voltage.VOLTAGE_230;
522 break;
523 case CurrentType.DC:
524 defaultVoltageOut = Voltage.VOLTAGE_400;
525 break;
526 default:
527 logger.error(`${logPrefix} ${errMsg}`);
528 throw new BaseError(errMsg);
529 }
530 return defaultVoltageOut;
531 }
532
e302df1d 533 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
15068be9 534 return (
e302df1d 535 stationInfo.idTagsFile &&
15068be9
JB
536 path.join(
537 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
538 'assets',
e302df1d 539 path.basename(stationInfo.idTagsFile)
15068be9
JB
540 )
541 );
542 }
543
52952bf8
JB
544 private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
545 connectorStatus.availability = AvailabilityType.Operative;
546 connectorStatus.idTagLocalAuthorized = false;
547 connectorStatus.idTagAuthorized = false;
548 connectorStatus.transactionRemoteStarted = false;
549 connectorStatus.transactionStarted = false;
550 connectorStatus.energyActiveImportRegisterValue = 0;
551 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
552 if (Utils.isUndefined(connectorStatus.chargingProfiles)) {
553 connectorStatus.chargingProfiles = [];
554 }
555 }
556
ae5020a3
JB
557 private static warnDeprecatedTemplateKey(
558 template: ChargingStationTemplate,
559 key: string,
560 templateFile: string,
561 logPrefix: string,
562 logMsgToAppend = ''
563 ): void {
564 if (!Utils.isUndefined(template[key])) {
565 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
566 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
567 }`;
568 logger.warn(`${logPrefix} ${logMsg}`);
569 console.warn(chalk.yellow(`${logMsg}`));
570 }
571 }
572
573 private static convertDeprecatedTemplateKey(
574 template: ChargingStationTemplate,
575 deprecatedKey: string,
576 key: string
577 ): void {
578 if (!Utils.isUndefined(template[deprecatedKey])) {
579 template[key] = template[deprecatedKey] as unknown;
580 delete template[deprecatedKey];
581 }
582 }
583
17ac262c 584 /**
54ebb82c 585 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
17ac262c 586 *
0e4fa348
JB
587 * @param chargingProfiles -
588 * @param logPrefix -
589 * @returns
17ac262c 590 */
15068be9 591 private static getLimitFromChargingProfiles(
17ac262c
JB
592 chargingProfiles: ChargingProfile[],
593 logPrefix: string
594 ): {
595 limit: number;
596 matchingChargingProfile: ChargingProfile;
597 } | null {
91a4f151 598 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
41189456
JB
599 const currentMoment = moment();
600 const currentDate = new Date();
17ac262c
JB
601 for (const chargingProfile of chargingProfiles) {
602 // Set helpers
17ac262c 603 const chargingSchedule = chargingProfile.chargingSchedule;
41189456
JB
604 if (!chargingSchedule?.startSchedule) {
605 logger.warn(
606 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
607 );
608 }
17ac262c
JB
609 // Check type (recurring) and if it is already active
610 // Adjust the daily recurring schedule to today
611 if (
612 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
613 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
614 currentMoment.isAfter(chargingSchedule.startSchedule)
615 ) {
41189456
JB
616 if (!(chargingSchedule?.startSchedule instanceof Date)) {
617 logger.warn(
618 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
619 );
620 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
621 }
17ac262c
JB
622 chargingSchedule.startSchedule.setFullYear(
623 currentDate.getFullYear(),
624 currentDate.getMonth(),
625 currentDate.getDate()
626 );
627 // Check if the start of the schedule is yesterday
628 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
629 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
630 }
631 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
632 return null;
633 }
634 // Check if the charging profile is active
635 if (
636 moment(chargingSchedule.startSchedule)
637 .add(chargingSchedule.duration, 's')
638 .isAfter(currentMoment)
639 ) {
640 let lastButOneSchedule: ChargingSchedulePeriod;
641 // Search the right schedule period
642 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
643 // Handling of only one period
644 if (
645 chargingSchedule.chargingSchedulePeriod.length === 1 &&
646 schedulePeriod.startPeriod === 0
647 ) {
648 const result = {
649 limit: schedulePeriod.limit,
650 matchingChargingProfile: chargingProfile,
651 };
91a4f151 652 logger.debug(debugLogMsg, result);
17ac262c
JB
653 return result;
654 }
655 // Find the right schedule period
656 if (
657 moment(chargingSchedule.startSchedule)
658 .add(schedulePeriod.startPeriod, 's')
659 .isAfter(currentMoment)
660 ) {
661 // Found the schedule: last but one is the correct one
662 const result = {
663 limit: lastButOneSchedule.limit,
664 matchingChargingProfile: chargingProfile,
665 };
91a4f151 666 logger.debug(debugLogMsg, result);
17ac262c
JB
667 return result;
668 }
669 // Keep it
670 lastButOneSchedule = schedulePeriod;
671 // Handle the last schedule period
672 if (
673 schedulePeriod.startPeriod ===
674 chargingSchedule.chargingSchedulePeriod[
675 chargingSchedule.chargingSchedulePeriod.length - 1
676 ].startPeriod
677 ) {
678 const result = {
679 limit: lastButOneSchedule.limit,
680 matchingChargingProfile: chargingProfile,
681 };
91a4f151 682 logger.debug(debugLogMsg, result);
17ac262c
JB
683 return result;
684 }
685 }
686 }
687 }
688 return null;
689 }
690
691 private static getRandomSerialNumberSuffix(params?: {
692 randomBytesLength?: number;
693 upperCase?: boolean;
694 }): string {
695 const randomSerialNumberSuffix = crypto
696 .randomBytes(params?.randomBytesLength ?? 16)
697 .toString('hex');
698 if (params?.upperCase) {
699 return randomSerialNumberSuffix.toUpperCase();
700 }
701 return randomSerialNumberSuffix;
702 }
703}