fix: fix worker options argument passing to worker pool/set
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
CommitLineData
43ee4373 1import crypto from 'node:crypto';
b1f1b0f6 2import type EventEmitter from 'node:events';
130783a7
JB
3import path from 'node:path';
4import { fileURLToPath } from 'node:url';
8114d10e 5
e302df1d 6import chalk from 'chalk';
8114d10e
JB
7import moment from 'moment';
8
4c3c0d59 9import type { ChargingStation } from './ChargingStation';
268a74bb 10import { BaseError } from '../exception';
981ebfbe 11import {
492cf6ab 12 AmpereUnits,
04b1261c 13 AvailabilityType,
268a74bb
JB
14 type BootNotificationRequest,
15 BootReasonEnumType,
15068be9 16 type ChargingProfile,
268a74bb 17 ChargingProfileKindType,
15068be9
JB
18 ChargingRateUnitType,
19 type ChargingSchedulePeriod,
268a74bb
JB
20 type ChargingStationInfo,
21 type ChargingStationTemplate,
b1f1b0f6 22 ChargingStationWorkerMessageEvents,
dd08d43d 23 ConnectorPhaseRotation,
a78ef5ed 24 type ConnectorStatus,
c3b83130 25 ConnectorStatusEnum,
268a74bb 26 CurrentType,
ae25f265 27 type EvseTemplate,
268a74bb
JB
28 type OCPP16BootNotificationRequest,
29 type OCPP20BootNotificationRequest,
30 OCPPVersion,
31 RecurrencyKindType,
32 Voltage,
33} from '../types';
aa7d6d95 34import { ACElectricUtils, Constants, DCElectricUtils, Utils, logger } from '../utils';
17ac262c 35
91a4f151
JB
36const moduleName = 'ChargingStationUtils';
37
17ac262c 38export class ChargingStationUtils {
d5bd1c00
JB
39 private constructor() {
40 // This is intentional
41 }
42
17ac262c
JB
43 public static getChargingStationId(
44 index: number,
45 stationTemplate: ChargingStationTemplate
46 ): string {
47 // In case of multiple instances: add instance index to charging station id
48 const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
1b271a54 49 const idSuffix = stationTemplate?.nameSuffix ?? '';
44eb6026 50 const idStr = `000000000${index.toString()}`;
ccb1d6e9 51 return stationTemplate?.fixedName
17ac262c 52 ? stationTemplate.baseName
44eb6026
JB
53 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
54 idStr.length - 4
55 )}${idSuffix}`;
17ac262c
JB
56 }
57
fa7bccf4 58 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
99e92237 59 const chargingStationInfo = {
fa7bccf4
JB
60 chargePointModel: stationTemplate.chargePointModel,
61 chargePointVendor: stationTemplate.chargePointVendor,
62 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
63 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
17ac262c 64 }),
fa7bccf4
JB
65 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
66 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
17ac262c 67 }),
fa7bccf4
JB
68 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
69 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
17ac262c 70 }),
fa7bccf4
JB
71 ...(!Utils.isUndefined(stationTemplate.meterType) && {
72 meterType: stationTemplate.meterType,
17ac262c
JB
73 }),
74 };
75 return crypto
76 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
fa7bccf4 77 .update(
14ecae6a
JB
78 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
79 index,
80 stationTemplate
81 )}`
fa7bccf4 82 )
17ac262c
JB
83 .digest('hex');
84 }
85
1bf29f5b
JB
86 public static checkChargingStation(chargingStation: ChargingStation, logPrefix: string): boolean {
87 if (chargingStation.started === false && chargingStation.starting === false) {
88 logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
89 return false;
90 }
91 return true;
92 }
93
be34dba5
JB
94 public static getPhaseRotationValue(
95 connectorId: number,
96 numberOfPhases: number
97 ): string | undefined {
dd08d43d
JB
98 // AC/DC
99 if (connectorId === 0 && numberOfPhases === 0) {
100 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
101 } else if (connectorId > 0 && numberOfPhases === 0) {
102 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
103 // AC
104 } else if (connectorId > 0 && numberOfPhases === 1) {
105 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
106 } else if (connectorId > 0 && numberOfPhases === 3) {
107 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
108 }
109 }
110
ae25f265
JB
111 public static getMaxNumberOfEvses(evses: Record<string, EvseTemplate>): number {
112 if (!evses) {
113 return -1;
114 }
115 return Object.keys(evses).length;
116 }
117
a78ef5ed
JB
118 public static getMaxNumberOfConnectors(connectors: Record<string, ConnectorStatus>): number {
119 if (!connectors) {
fa7bccf4
JB
120 return -1;
121 }
a78ef5ed 122 return Object.keys(connectors).length;
fa7bccf4
JB
123 }
124
c3b83130
JB
125 public static getBootConnectorStatus(
126 chargingStation: ChargingStation,
127 connectorId: number,
128 connectorStatus: ConnectorStatus
129 ): ConnectorStatusEnum {
130 let connectorBootStatus: ConnectorStatusEnum;
131 if (
132 !connectorStatus?.status &&
133 (chargingStation.isChargingStationAvailable() === false ||
134 chargingStation.isConnectorAvailable(connectorId) === false)
135 ) {
136 connectorBootStatus = ConnectorStatusEnum.Unavailable;
137 } else if (!connectorStatus?.status && connectorStatus?.bootStatus) {
138 // Set boot status in template at startup
139 connectorBootStatus = connectorStatus?.bootStatus;
140 } else if (connectorStatus?.status) {
141 // Set previous status at startup
142 connectorBootStatus = connectorStatus?.status;
143 } else {
144 // Set default status
145 connectorBootStatus = ConnectorStatusEnum.Available;
146 }
147 return connectorBootStatus;
148 }
149
e1893686 150 public static checkTemplate(
8a133cc8
JB
151 stationTemplate: ChargingStationTemplate,
152 logPrefix: string,
153 templateFile: string
154 ) {
155 if (Utils.isNullOrUndefined(stationTemplate)) {
156 const errorMsg = `Failed to read charging station template file ${templateFile}`;
157 logger.error(`${logPrefix} ${errorMsg}`);
158 throw new BaseError(errorMsg);
159 }
160 if (Utils.isEmptyObject(stationTemplate)) {
161 const errorMsg = `Empty charging station information from template file ${templateFile}`;
162 logger.error(`${logPrefix} ${errorMsg}`);
163 throw new BaseError(errorMsg);
164 }
ac7f79af 165 if (Utils.isEmptyObject(stationTemplate.AutomaticTransactionGenerator)) {
1fe0632a 166 stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION;
ac7f79af 167 logger.warn(
1fe0632a
JB
168 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
169 Constants.DEFAULT_ATG_CONFIGURATION
ac7f79af
JB
170 );
171 }
111aaf89
JB
172 if (
173 Utils.isNullOrUndefined(stationTemplate.idTagsFile) ||
174 Utils.isEmptyString(stationTemplate.idTagsFile)
175 ) {
176 logger.warn(
177 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
178 );
179 }
8a133cc8
JB
180 }
181
cda5d0fb 182 public static checkConnectorsConfiguration(
8a133cc8
JB
183 stationTemplate: ChargingStationTemplate,
184 logPrefix: string,
185 templateFile: string
cda5d0fb
JB
186 ): {
187 configuredMaxConnectors: number;
188 templateMaxConnectors: number;
189 templateMaxAvailableConnectors: number;
190 } {
191 const configuredMaxConnectors =
192 ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate);
193 ChargingStationUtils.checkConfiguredMaxConnectors(
194 configuredMaxConnectors,
8a133cc8
JB
195 logPrefix,
196 templateFile
cda5d0fb
JB
197 );
198 const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
199 stationTemplate.Connectors
200 );
8a133cc8 201 ChargingStationUtils.checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile);
cda5d0fb
JB
202 const templateMaxAvailableConnectors = stationTemplate?.Connectors[0]
203 ? templateMaxConnectors - 1
204 : templateMaxConnectors;
205 if (
206 configuredMaxConnectors > templateMaxAvailableConnectors &&
207 !stationTemplate?.randomConnectors
208 ) {
fa7bccf4 209 logger.warn(
cda5d0fb 210 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
fa7bccf4 211 );
cda5d0fb 212 stationTemplate.randomConnectors = true;
fa7bccf4 213 }
cda5d0fb 214 return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors };
fa7bccf4
JB
215 }
216
04b1261c
JB
217 public static checkStationInfoConnectorStatus(
218 connectorId: number,
219 connectorStatus: ConnectorStatus,
220 logPrefix: string,
221 templateFile: string
222 ): void {
223 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
224 logger.warn(
54ebb82c 225 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
04b1261c
JB
226 );
227 delete connectorStatus.status;
228 }
229 }
230
231 public static buildConnectorsMap(
232 connectors: Record<string, ConnectorStatus>,
233 logPrefix: string,
234 templateFile: string
235 ): Map<number, ConnectorStatus> {
236 const connectorsMap = new Map<number, ConnectorStatus>();
ae25f265
JB
237 if (ChargingStationUtils.getMaxNumberOfConnectors(connectors) > 0) {
238 for (const connector in connectors) {
239 const connectorStatus = connectors[connector];
240 const connectorId = Utils.convertToInt(connector);
241 ChargingStationUtils.checkStationInfoConnectorStatus(
242 connectorId,
243 connectorStatus,
244 logPrefix,
245 templateFile
246 );
247 connectorsMap.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
04b1261c 248 }
ae25f265
JB
249 } else {
250 logger.warn(
251 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
252 );
04b1261c
JB
253 }
254 return connectorsMap;
255 }
256
257 public static initializeConnectorsMapStatus(
258 connectors: Map<number, ConnectorStatus>,
259 logPrefix: string
260 ): void {
261 for (const connectorId of connectors.keys()) {
262 if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
263 logger.warn(
54ebb82c 264 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
04b1261c
JB
265 connectors.get(connectorId)?.transactionId
266 }`
267 );
268 }
8e2430ee 269 if (connectorId === 0) {
52952bf8
JB
270 connectors.get(connectorId).availability = AvailabilityType.Operative;
271 if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
272 connectors.get(connectorId).chargingProfiles = [];
273 }
274 } else if (
04b1261c
JB
275 connectorId > 0 &&
276 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
277 ) {
278 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
279 }
280 }
281 }
282
04b1261c
JB
283 public static resetConnectorStatus(connectorStatus: ConnectorStatus): void {
284 connectorStatus.idTagLocalAuthorized = false;
285 connectorStatus.idTagAuthorized = false;
286 connectorStatus.transactionRemoteStarted = false;
287 connectorStatus.transactionStarted = false;
288 delete connectorStatus?.localAuthorizeIdTag;
289 delete connectorStatus?.authorizeIdTag;
290 delete connectorStatus?.transactionId;
291 delete connectorStatus?.transactionIdTag;
292 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
293 delete connectorStatus?.transactionBeginMeterValue;
294 }
295
17ac262c 296 public static createBootNotificationRequest(
15068be9
JB
297 stationInfo: ChargingStationInfo,
298 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
17ac262c 299 ): BootNotificationRequest {
d270cc87
JB
300 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
301 switch (ocppVersion) {
302 case OCPPVersion.VERSION_16:
303 return {
304 chargePointModel: stationInfo.chargePointModel,
305 chargePointVendor: stationInfo.chargePointVendor,
306 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
307 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
308 }),
309 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
310 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
311 }),
312 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
313 firmwareVersion: stationInfo.firmwareVersion,
314 }),
315 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
316 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
317 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
318 meterSerialNumber: stationInfo.meterSerialNumber,
319 }),
320 ...(!Utils.isUndefined(stationInfo.meterType) && {
321 meterType: stationInfo.meterType,
322 }),
323 } as OCPP16BootNotificationRequest;
324 case OCPPVersion.VERSION_20:
325 case OCPPVersion.VERSION_201:
326 return {
15068be9 327 reason: bootReason,
d270cc87
JB
328 chargingStation: {
329 model: stationInfo.chargePointModel,
330 vendorName: stationInfo.chargePointVendor,
331 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
332 firmwareVersion: stationInfo.firmwareVersion,
333 }),
334 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
335 serialNumber: stationInfo.chargeBoxSerialNumber,
336 }),
98fc1389
JB
337 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
338 modem: {
339 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
340 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
341 },
342 }),
d270cc87
JB
343 },
344 } as OCPP20BootNotificationRequest;
345 }
17ac262c
JB
346 }
347
ae5020a3 348 public static warnTemplateKeysDeprecation(
ae5020a3 349 stationTemplate: ChargingStationTemplate,
8a133cc8
JB
350 logPrefix: string,
351 templateFile: string
ae5020a3
JB
352 ) {
353 const templateKeys: { key: string; deprecatedKey: string }[] = [
354 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
355 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
356 ];
357 for (const templateKey of templateKeys) {
358 ChargingStationUtils.warnDeprecatedTemplateKey(
359 stationTemplate,
360 templateKey.deprecatedKey,
ae5020a3 361 logPrefix,
8a133cc8 362 templateFile,
ae5020a3
JB
363 `Use '${templateKey.key}' instead`
364 );
365 ChargingStationUtils.convertDeprecatedTemplateKey(
366 stationTemplate,
367 templateKey.deprecatedKey,
368 templateKey.key
369 );
17ac262c
JB
370 }
371 }
372
fa7bccf4
JB
373 public static stationTemplateToStationInfo(
374 stationTemplate: ChargingStationTemplate
375 ): ChargingStationInfo {
8df5ae48 376 stationTemplate = Utils.cloneObject<ChargingStationTemplate>(stationTemplate);
fa7bccf4
JB
377 delete stationTemplate.power;
378 delete stationTemplate.powerUnit;
8a133cc8
JB
379 delete stationTemplate?.Connectors;
380 delete stationTemplate?.Evses;
fa7bccf4
JB
381 delete stationTemplate.Configuration;
382 delete stationTemplate.AutomaticTransactionGenerator;
383 delete stationTemplate.chargeBoxSerialNumberPrefix;
384 delete stationTemplate.chargePointSerialNumberPrefix;
fec4d204 385 delete stationTemplate.meterSerialNumberPrefix;
51c83d6f 386 return stationTemplate as unknown as ChargingStationInfo;
fa7bccf4
JB
387 }
388
17ac262c 389 public static createSerialNumber(
fa7bccf4 390 stationTemplate: ChargingStationTemplate,
4d20f040 391 stationInfo: ChargingStationInfo,
fa7bccf4
JB
392 params: {
393 randomSerialNumberUpperCase?: boolean;
394 randomSerialNumber?: boolean;
395 } = {
17ac262c
JB
396 randomSerialNumberUpperCase: true,
397 randomSerialNumber: true,
398 }
399 ): void {
f832e5df 400 params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params };
fa7bccf4
JB
401 const serialNumberSuffix = params?.randomSerialNumber
402 ? ChargingStationUtils.getRandomSerialNumberSuffix({
403 upperCase: params.randomSerialNumberUpperCase,
404 })
405 : '';
f832e5df
JB
406 Utils.isNotEmptyString(stationTemplate?.chargePointSerialNumberPrefix) &&
407 (stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
408 Utils.isNotEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) &&
409 (stationInfo.chargeBoxSerialNumber = `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
410 Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix) &&
411 (stationInfo.meterSerialNumber = `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
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 457 const chargingProfiles =
8df5ae48
JB
458 Utils.cloneObject<ChargingProfile[]>(
459 chargingStation.getConnectorStatus(connectorId)?.chargingProfiles
460 )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? [];
15068be9 461 // Get profiles on connector 0
1895299d 462 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
15068be9 463 chargingProfiles.push(
8df5ae48
JB
464 ...Utils.cloneObject<ChargingProfile[]>(
465 chargingStation.getConnectorStatus(0).chargingProfiles
466 ).sort((a, b) => b.stackLevel - a.stackLevel)
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,
8a133cc8
JB
514 logPrefix: string,
515 templateFile: string
15068be9 516 ): Voltage {
ded57f02 517 const errorMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
15068be9
JB
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:
ded57f02
JB
527 logger.error(`${logPrefix} ${errorMsg}`);
528 throw new BaseError(errorMsg);
15068be9
JB
529 }
530 return defaultVoltageOut;
531 }
532
e302df1d 533 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
15068be9 534 return (
e302df1d 535 stationInfo.idTagsFile &&
15068be9 536 path.join(
51022aa0 537 path.dirname(fileURLToPath(import.meta.url)),
15068be9 538 'assets',
e302df1d 539 path.basename(stationInfo.idTagsFile)
15068be9
JB
540 )
541 );
542 }
543
b1f1b0f6
JB
544 public static waitForChargingStationEvents = async (
545 emitter: EventEmitter,
546 event: ChargingStationWorkerMessageEvents,
547 eventsToWait: number
548 ): Promise<number> => {
549 return new Promise((resolve) => {
550 let events = 0;
551 if (eventsToWait === 0) {
552 resolve(events);
553 }
554 emitter.on(event, () => {
555 ++events;
556 if (events === eventsToWait) {
557 resolve(events);
558 }
559 });
560 });
561 };
562
8a133cc8 563 private static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
cda5d0fb
JB
564 let configuredMaxConnectors: number;
565 if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
566 const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
567 configuredMaxConnectors =
568 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
569 } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) {
570 configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
571 } else if (stationTemplate.Connectors && !stationTemplate.Evses) {
572 configuredMaxConnectors = stationTemplate?.Connectors[0]
573 ? ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
574 : ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors);
575 } else if (stationTemplate.Evses && !stationTemplate.Connectors) {
576 configuredMaxConnectors = 0;
577 for (const evse in stationTemplate.Evses) {
578 if (evse === '0') {
579 continue;
580 }
581 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
582 stationTemplate.Evses[evse].Connectors
583 );
584 }
585 }
586 return configuredMaxConnectors;
587 }
588
589 private static checkConfiguredMaxConnectors(
590 configuredMaxConnectors: number,
8a133cc8
JB
591 logPrefix: string,
592 templateFile: string
cda5d0fb
JB
593 ): void {
594 if (configuredMaxConnectors <= 0) {
595 logger.warn(
596 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
597 );
598 }
599 }
600
601 private static checkTemplateMaxConnectors(
602 templateMaxConnectors: number,
8a133cc8
JB
603 logPrefix: string,
604 templateFile: string
cda5d0fb
JB
605 ): void {
606 if (templateMaxConnectors === 0) {
607 logger.warn(
608 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
609 );
610 } else if (templateMaxConnectors < 0) {
611 logger.error(
612 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
613 );
614 }
615 }
616
52952bf8
JB
617 private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
618 connectorStatus.availability = AvailabilityType.Operative;
619 connectorStatus.idTagLocalAuthorized = false;
620 connectorStatus.idTagAuthorized = false;
621 connectorStatus.transactionRemoteStarted = false;
622 connectorStatus.transactionStarted = false;
623 connectorStatus.energyActiveImportRegisterValue = 0;
624 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
625 if (Utils.isUndefined(connectorStatus.chargingProfiles)) {
626 connectorStatus.chargingProfiles = [];
627 }
628 }
629
ae5020a3
JB
630 private static warnDeprecatedTemplateKey(
631 template: ChargingStationTemplate,
632 key: string,
ae5020a3 633 logPrefix: string,
8a133cc8 634 templateFile: string,
ae5020a3
JB
635 logMsgToAppend = ''
636 ): void {
637 if (!Utils.isUndefined(template[key])) {
638 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
639 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
640 }`;
641 logger.warn(`${logPrefix} ${logMsg}`);
642 console.warn(chalk.yellow(`${logMsg}`));
643 }
644 }
645
646 private static convertDeprecatedTemplateKey(
647 template: ChargingStationTemplate,
648 deprecatedKey: string,
649 key: string
650 ): void {
651 if (!Utils.isUndefined(template[deprecatedKey])) {
652 template[key] = template[deprecatedKey] as unknown;
653 delete template[deprecatedKey];
654 }
655 }
656
17ac262c 657 /**
54ebb82c 658 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
17ac262c 659 *
0e4fa348
JB
660 * @param chargingProfiles -
661 * @param logPrefix -
662 * @returns
17ac262c 663 */
15068be9 664 private static getLimitFromChargingProfiles(
17ac262c
JB
665 chargingProfiles: ChargingProfile[],
666 logPrefix: string
667 ): {
668 limit: number;
669 matchingChargingProfile: ChargingProfile;
670 } | null {
91a4f151 671 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
41189456
JB
672 const currentMoment = moment();
673 const currentDate = new Date();
17ac262c
JB
674 for (const chargingProfile of chargingProfiles) {
675 // Set helpers
17ac262c 676 const chargingSchedule = chargingProfile.chargingSchedule;
41189456
JB
677 if (!chargingSchedule?.startSchedule) {
678 logger.warn(
679 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
680 );
681 }
17ac262c
JB
682 // Check type (recurring) and if it is already active
683 // Adjust the daily recurring schedule to today
684 if (
685 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
686 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
687 currentMoment.isAfter(chargingSchedule.startSchedule)
688 ) {
41189456
JB
689 if (!(chargingSchedule?.startSchedule instanceof Date)) {
690 logger.warn(
691 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
692 );
693 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
694 }
17ac262c
JB
695 chargingSchedule.startSchedule.setFullYear(
696 currentDate.getFullYear(),
697 currentDate.getMonth(),
698 currentDate.getDate()
699 );
700 // Check if the start of the schedule is yesterday
701 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
702 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
703 }
704 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
705 return null;
706 }
707 // Check if the charging profile is active
708 if (
709 moment(chargingSchedule.startSchedule)
710 .add(chargingSchedule.duration, 's')
711 .isAfter(currentMoment)
712 ) {
713 let lastButOneSchedule: ChargingSchedulePeriod;
714 // Search the right schedule period
715 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
716 // Handling of only one period
717 if (
718 chargingSchedule.chargingSchedulePeriod.length === 1 &&
719 schedulePeriod.startPeriod === 0
720 ) {
721 const result = {
722 limit: schedulePeriod.limit,
723 matchingChargingProfile: chargingProfile,
724 };
91a4f151 725 logger.debug(debugLogMsg, result);
17ac262c
JB
726 return result;
727 }
728 // Find the right schedule period
729 if (
730 moment(chargingSchedule.startSchedule)
731 .add(schedulePeriod.startPeriod, 's')
732 .isAfter(currentMoment)
733 ) {
734 // Found the schedule: last but one is the correct one
735 const result = {
736 limit: lastButOneSchedule.limit,
737 matchingChargingProfile: chargingProfile,
738 };
91a4f151 739 logger.debug(debugLogMsg, result);
17ac262c
JB
740 return result;
741 }
742 // Keep it
743 lastButOneSchedule = schedulePeriod;
744 // Handle the last schedule period
745 if (
746 schedulePeriod.startPeriod ===
747 chargingSchedule.chargingSchedulePeriod[
748 chargingSchedule.chargingSchedulePeriod.length - 1
749 ].startPeriod
750 ) {
751 const result = {
752 limit: lastButOneSchedule.limit,
753 matchingChargingProfile: chargingProfile,
754 };
91a4f151 755 logger.debug(debugLogMsg, result);
17ac262c
JB
756 return result;
757 }
758 }
759 }
760 }
761 return null;
762 }
763
764 private static getRandomSerialNumberSuffix(params?: {
765 randomBytesLength?: number;
766 upperCase?: boolean;
767 }): string {
768 const randomSerialNumberSuffix = crypto
769 .randomBytes(params?.randomBytesLength ?? 16)
770 .toString('hex');
771 if (params?.upperCase) {
772 return randomSerialNumberSuffix.toUpperCase();
773 }
774 return randomSerialNumberSuffix;
775 }
776}