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