fix(simulator): make the modules export/import a bit less sensitive to
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
b4d34251 2
43ee4373 3import crypto from 'node:crypto';
130783a7
JB
4import fs from 'node:fs';
5import path from 'node:path';
6import { URL } from 'node:url';
8114d10e
JB
7import { parentPort } from 'worker_threads';
8
5d280aae 9import merge from 'just-merge';
ef7d8c21 10import WebSocket, { type RawData } from 'ws';
8114d10e 11
268a74bb
JB
12import { AuthorizedTagsCache } from './AuthorizedTagsCache';
13import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator';
78202038
JB
14import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
15import { ChargingStationUtils } from './ChargingStationUtils';
268a74bb 16import { ChargingStationWorkerBroadcastChannel } from './ChargingStationWorkerBroadcastChannel';
78202038 17import { MessageChannelUtils } from './MessageChannelUtils';
368a6eda
JB
18import {
19 OCPP16IncomingRequestService,
20 OCPP16RequestService,
21 OCPP16ResponseService,
22 OCPP16ServiceUtils,
23 OCPP20IncomingRequestService,
24 OCPP20RequestService,
25 OCPP20ResponseService,
26 type OCPPIncomingRequestService,
27 type OCPPRequestService,
28 OCPPServiceUtils,
29} from './ocpp';
268a74bb
JB
30import { SharedLRUCache } from './SharedLRUCache';
31import { BaseError, OCPPError } from '../exception';
17bc43d7 32import { PerformanceStatistics } from '../performance';
e7aeea18 33import {
268a74bb 34 type AutomaticTransactionGeneratorConfiguration,
e7aeea18 35 AvailabilityType,
e0b0ee21 36 type BootNotificationRequest,
268a74bb 37 type BootNotificationResponse,
e0b0ee21 38 type CachedRequest,
268a74bb
JB
39 type ChargingStationConfiguration,
40 type ChargingStationInfo,
41 type ChargingStationOcppConfiguration,
42 type ChargingStationTemplate,
43 ConnectorPhaseRotation,
44 ConnectorStatus,
45 ConnectorStatusEnum,
46 CurrentType,
e0b0ee21 47 type ErrorCallback,
268a74bb
JB
48 type ErrorResponse,
49 ErrorType,
50 FileType,
c9a4f9ea
JB
51 FirmwareStatus,
52 type FirmwareStatusNotificationRequest,
268a74bb
JB
53 type FirmwareStatusNotificationResponse,
54 type FirmwareUpgrade,
e0b0ee21 55 type HeartbeatRequest,
268a74bb 56 type HeartbeatResponse,
e0b0ee21 57 type IncomingRequest,
268a74bb
JB
58 type IncomingRequestCommand,
59 type JsonType,
60 MessageType,
61 type MeterValue,
62 MeterValueMeasurand,
e0b0ee21 63 type MeterValuesRequest,
268a74bb
JB
64 type MeterValuesResponse,
65 OCPPVersion,
8ca6874c 66 type OutgoingRequest,
268a74bb
JB
67 PowerUnits,
68 RegistrationStatusEnumType,
e7aeea18 69 RequestCommand,
268a74bb 70 type Response,
e0b0ee21 71 type ResponseCallback,
268a74bb 72 StandardParametersKey,
e0b0ee21 73 type StatusNotificationRequest,
e0b0ee21 74 type StatusNotificationResponse,
ef6fa3fb 75 StopTransactionReason,
e0b0ee21
JB
76 type StopTransactionRequest,
77 type StopTransactionResponse,
268a74bb
JB
78 SupervisionUrlDistribution,
79 SupportedFeatureProfiles,
80 VendorDefaultParametersKey,
81 type WSError,
82 WebSocketCloseEventStatusCode,
83 type WsOptions,
84} from '../types';
85import { Configuration } from '../utils/Configuration';
86import { Constants } from '../utils/Constants';
8114d10e 87import { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils';
268a74bb
JB
88import { FileUtils } from '../utils/FileUtils';
89import { logger } from '../utils/Logger';
90import { Utils } from '../utils/Utils';
3f40bc9c 91
268a74bb 92export class ChargingStation {
c72f6634 93 public readonly index: number;
2484ac1e 94 public readonly templateFile: string;
6e0964c8 95 public stationInfo!: ChargingStationInfo;
452a82ca 96 public started: boolean;
cbf9b878 97 public starting: boolean;
a5e9befc 98 public authorizedTagsCache: AuthorizedTagsCache;
551e477c
JB
99 public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
100 public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
72092cfc 101 public wsConnection!: WebSocket | null;
a5e9befc 102 public readonly connectors: Map<number, ConnectorStatus>;
9e23580d 103 public readonly requests: Map<string, CachedRequest>;
551e477c 104 public performanceStatistics!: PerformanceStatistics | undefined;
6e0964c8 105 public heartbeatSetInterval!: NodeJS.Timeout;
6e0964c8 106 public ocppRequestService!: OCPPRequestService;
0a03f36c 107 public bootNotificationRequest!: BootNotificationRequest;
1895299d 108 public bootNotificationResponse!: BootNotificationResponse | undefined;
fa7bccf4 109 public powerDivider!: number;
950b1349 110 private stopping: boolean;
073bd098 111 private configurationFile!: string;
7c72977b 112 private configurationFileHash!: string;
6e0964c8 113 private connectorsConfigurationHash!: string;
a472cf2b 114 private ocppIncomingRequestService!: OCPPIncomingRequestService;
8e242273 115 private readonly messageBuffer: Set<string>;
fa7bccf4 116 private configuredSupervisionUrl!: URL;
c72f6634 117 private configuredSupervisionUrlIndex!: number;
265e4266 118 private wsConnectionRestarted: boolean;
ad2f27c3 119 private autoReconnectRetryCount: number;
72092cfc 120 private templateFileWatcher!: fs.FSWatcher | undefined;
57adbebc 121 private readonly sharedLRUCache: SharedLRUCache;
6e0964c8 122 private webSocketPingSetInterval!: NodeJS.Timeout;
89b7a234 123 private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
6af9012e 124
2484ac1e 125 constructor(index: number, templateFile: string) {
aa428a31 126 this.started = false;
950b1349
JB
127 this.starting = false;
128 this.stopping = false;
aa428a31
JB
129 this.wsConnectionRestarted = false;
130 this.autoReconnectRetryCount = 0;
ad2f27c3 131 this.index = index;
2484ac1e 132 this.templateFile = templateFile;
9f2e3130 133 this.connectors = new Map<number, ConnectorStatus>();
32b02249 134 this.requests = new Map<string, CachedRequest>();
8e242273 135 this.messageBuffer = new Set<string>();
b44b779a
JB
136 this.sharedLRUCache = SharedLRUCache.getInstance();
137 this.authorizedTagsCache = AuthorizedTagsCache.getInstance();
89b7a234 138 this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
32de5a57 139
9f2e3130 140 this.initialize();
c0560973
JB
141 }
142
25f5a959 143 private get wsConnectionUrl(): URL {
fa7bccf4 144 return new URL(
44eb6026
JB
145 `${
146 this.getSupervisionUrlOcppConfiguration()
147 ? ChargingStationConfigurationUtils.getConfigurationKey(
148 this,
149 this.getSupervisionUrlOcppKey()
72092cfc 150 )?.value
44eb6026
JB
151 : this.configuredSupervisionUrl.href
152 }/${this.stationInfo.chargingStationId}`
fa7bccf4 153 );
12fc74d6
JB
154 }
155
8b7072dc 156 public logPrefix = (): string => {
ccb1d6e9
JB
157 return Utils.logPrefix(
158 ` ${
5a2a53cf 159 (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId) &&
1b271a54
JB
160 this?.stationInfo?.chargingStationId) ??
161 ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile()) ??
162 ''
ccb1d6e9
JB
163 } |`
164 );
8b7072dc 165 };
c0560973 166
c0560973 167 public hasAuthorizedTags(): boolean {
53ac516c 168 return Utils.isNotEmptyArray(
9d7484a4
JB
169 this.authorizedTagsCache.getAuthorizedTags(
170 ChargingStationUtils.getAuthorizationFile(this.stationInfo)
171 )
172 );
c0560973
JB
173 }
174
ad774cec
JB
175 public getEnableStatistics(): boolean {
176 return this.stationInfo.enableStatistics ?? false;
c0560973
JB
177 }
178
ad774cec 179 public getMustAuthorizeAtRemoteStart(): boolean {
03ebf4c1 180 return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
a7fc8211
JB
181 }
182
ad774cec 183 public getPayloadSchemaValidation(): boolean {
e3018bc4
JB
184 return this.stationInfo.payloadSchemaValidation ?? true;
185 }
186
fa7bccf4
JB
187 public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
188 const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
189 switch (this.getCurrentOutType(stationInfo)) {
4c2b4904 190 case CurrentType.AC:
fa7bccf4
JB
191 return !Utils.isUndefined(localStationInfo.numberOfPhases)
192 ? localStationInfo.numberOfPhases
e7aeea18 193 : 3;
4c2b4904 194 case CurrentType.DC:
c0560973
JB
195 return 0;
196 }
197 }
198
d5bff457 199 public isWebSocketConnectionOpened(): boolean {
0d8140bd 200 return this?.wsConnection?.readyState === WebSocket.OPEN;
c0560973
JB
201 }
202
1895299d 203 public getRegistrationStatus(): RegistrationStatusEnumType | undefined {
672fed6e
JB
204 return this?.bootNotificationResponse?.status;
205 }
206
73c4266d
JB
207 public isInUnknownState(): boolean {
208 return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
209 }
210
16cd35ad 211 public isInPendingState(): boolean {
d270cc87 212 return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING;
16cd35ad
JB
213 }
214
215 public isInAcceptedState(): boolean {
d270cc87 216 return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED;
c0560973
JB
217 }
218
16cd35ad 219 public isInRejectedState(): boolean {
d270cc87 220 return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED;
16cd35ad
JB
221 }
222
223 public isRegistered(): boolean {
ed6cfcff
JB
224 return (
225 this.isInUnknownState() === false &&
226 (this.isInAcceptedState() === true || this.isInPendingState() === true)
227 );
16cd35ad
JB
228 }
229
c0560973 230 public isChargingStationAvailable(): boolean {
1895299d 231 return this.getConnectorStatus(0)?.availability === AvailabilityType.OPERATIVE;
c0560973
JB
232 }
233
234 public isConnectorAvailable(id: number): boolean {
1895299d 235 return id > 0 && this.getConnectorStatus(id)?.availability === AvailabilityType.OPERATIVE;
c0560973
JB
236 }
237
54544ef1
JB
238 public getNumberOfConnectors(): number {
239 return this.connectors.get(0) ? this.connectors.size - 1 : this.connectors.size;
240 }
241
6d9876e7 242 public getConnectorStatus(id: number): ConnectorStatus | undefined {
734d790d 243 return this.connectors.get(id);
c0560973
JB
244 }
245
fa7bccf4 246 public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
72092cfc 247 return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC;
c0560973
JB
248 }
249
672fed6e 250 public getOcppStrictCompliance(): boolean {
ccb1d6e9 251 return this.stationInfo?.ocppStrictCompliance ?? false;
672fed6e
JB
252 }
253
fa7bccf4 254 public getVoltageOut(stationInfo?: ChargingStationInfo): number | undefined {
492cf6ab 255 const defaultVoltageOut = ChargingStationUtils.getDefaultVoltageOut(
fa7bccf4 256 this.getCurrentOutType(stationInfo),
492cf6ab
JB
257 this.templateFile,
258 this.logPrefix()
259 );
fa7bccf4
JB
260 const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
261 return !Utils.isUndefined(localStationInfo.voltageOut)
262 ? localStationInfo.voltageOut
e7aeea18 263 : defaultVoltageOut;
c0560973
JB
264 }
265
15068be9
JB
266 public getMaximumPower(stationInfo?: ChargingStationInfo): number {
267 const localStationInfo = stationInfo ?? this.stationInfo;
268 return (localStationInfo['maxPower'] as number) ?? localStationInfo.maximumPower;
269 }
270
ad8537a7 271 public getConnectorMaximumAvailablePower(connectorId: number): number {
d20f43b5 272 let connectorAmperageLimitationPowerLimit: number;
b47d68d7
JB
273 if (
274 !Utils.isNullOrUndefined(this.getAmperageLimitation()) &&
72092cfc 275 this.getAmperageLimitation() < this.stationInfo?.maximumAmperage
b47d68d7 276 ) {
4160ae28
JB
277 connectorAmperageLimitationPowerLimit =
278 (this.getCurrentOutType() === CurrentType.AC
cc6e8ab5
JB
279 ? ACElectricUtils.powerTotal(
280 this.getNumberOfPhases(),
281 this.getVoltageOut(),
da57964c 282 this.getAmperageLimitation() * this.getNumberOfConnectors()
cc6e8ab5 283 )
4160ae28 284 : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation())) /
fa7bccf4 285 this.powerDivider;
cc6e8ab5 286 }
fa7bccf4 287 const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
15068be9
JB
288 const connectorChargingProfilesPowerLimit =
289 ChargingStationUtils.getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
ad8537a7
JB
290 return Math.min(
291 isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower,
292 isNaN(connectorAmperageLimitationPowerLimit)
293 ? Infinity
294 : connectorAmperageLimitationPowerLimit,
15068be9 295 isNaN(connectorChargingProfilesPowerLimit) ? Infinity : connectorChargingProfilesPowerLimit
ad8537a7 296 );
cc6e8ab5
JB
297 }
298
6e0964c8 299 public getTransactionIdTag(transactionId: number): string | undefined {
734d790d 300 for (const connectorId of this.connectors.keys()) {
1895299d
JB
301 if (
302 connectorId > 0 &&
303 this.getConnectorStatus(connectorId)?.transactionId === transactionId
304 ) {
72092cfc 305 return this.getConnectorStatus(connectorId)?.transactionIdTag;
c0560973
JB
306 }
307 }
308 }
309
6ed92bc1 310 public getOutOfOrderEndMeterValues(): boolean {
ccb1d6e9 311 return this.stationInfo?.outOfOrderEndMeterValues ?? false;
6ed92bc1
JB
312 }
313
314 public getBeginEndMeterValues(): boolean {
ccb1d6e9 315 return this.stationInfo?.beginEndMeterValues ?? false;
6ed92bc1
JB
316 }
317
318 public getMeteringPerTransaction(): boolean {
ccb1d6e9 319 return this.stationInfo?.meteringPerTransaction ?? true;
6ed92bc1
JB
320 }
321
fd0c36fa 322 public getTransactionDataMeterValues(): boolean {
ccb1d6e9 323 return this.stationInfo?.transactionDataMeterValues ?? false;
fd0c36fa
JB
324 }
325
9ccca265 326 public getMainVoltageMeterValues(): boolean {
ccb1d6e9 327 return this.stationInfo?.mainVoltageMeterValues ?? true;
9ccca265
JB
328 }
329
6b10669b 330 public getPhaseLineToLineVoltageMeterValues(): boolean {
ccb1d6e9 331 return this.stationInfo?.phaseLineToLineVoltageMeterValues ?? false;
9bd87386
JB
332 }
333
7bc31f9c 334 public getCustomValueLimitationMeterValues(): boolean {
ccb1d6e9 335 return this.stationInfo?.customValueLimitationMeterValues ?? true;
7bc31f9c
JB
336 }
337
f479a792 338 public getConnectorIdByTransactionId(transactionId: number): number | undefined {
734d790d 339 for (const connectorId of this.connectors.keys()) {
f479a792
JB
340 if (
341 connectorId > 0 &&
342 this.getConnectorStatus(connectorId)?.transactionId === transactionId
343 ) {
344 return connectorId;
c0560973
JB
345 }
346 }
347 }
348
07989fad
JB
349 public getEnergyActiveImportRegisterByTransactionId(
350 transactionId: number,
18bf8274 351 rounded = false
07989fad
JB
352 ): number {
353 return this.getEnergyActiveImportRegister(
354 this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)),
18bf8274 355 rounded
cbad1217 356 );
cbad1217
JB
357 }
358
18bf8274
JB
359 public getEnergyActiveImportRegisterByConnectorId(connectorId: number, rounded = false): number {
360 return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded);
6ed92bc1
JB
361 }
362
c0560973 363 public getAuthorizeRemoteTxRequests(): boolean {
17ac262c
JB
364 const authorizeRemoteTxRequests = ChargingStationConfigurationUtils.getConfigurationKey(
365 this,
e7aeea18
JB
366 StandardParametersKey.AuthorizeRemoteTxRequests
367 );
368 return authorizeRemoteTxRequests
369 ? Utils.convertToBoolean(authorizeRemoteTxRequests.value)
370 : false;
c0560973
JB
371 }
372
373 public getLocalAuthListEnabled(): boolean {
17ac262c
JB
374 const localAuthListEnabled = ChargingStationConfigurationUtils.getConfigurationKey(
375 this,
e7aeea18
JB
376 StandardParametersKey.LocalAuthListEnabled
377 );
c0560973
JB
378 return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false;
379 }
380
c0560973 381 public startHeartbeat(): void {
e7aeea18
JB
382 if (
383 this.getHeartbeatInterval() &&
384 this.getHeartbeatInterval() > 0 &&
385 !this.heartbeatSetInterval
386 ) {
6a8329b4
JB
387 this.heartbeatSetInterval = setInterval(() => {
388 this.ocppRequestService
389 .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
72092cfc 390 .catch((error) => {
6a8329b4
JB
391 logger.error(
392 `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
393 error
394 );
395 });
c0560973 396 }, this.getHeartbeatInterval());
e7aeea18 397 logger.info(
44eb6026
JB
398 `${this.logPrefix()} Heartbeat started every ${Utils.formatDurationMilliSeconds(
399 this.getHeartbeatInterval()
400 )}`
e7aeea18 401 );
c0560973 402 } else if (this.heartbeatSetInterval) {
e7aeea18 403 logger.info(
44eb6026
JB
404 `${this.logPrefix()} Heartbeat already started every ${Utils.formatDurationMilliSeconds(
405 this.getHeartbeatInterval()
406 )}`
e7aeea18 407 );
c0560973 408 } else {
e7aeea18
JB
409 logger.error(
410 `${this.logPrefix()} Heartbeat interval set to ${
411 this.getHeartbeatInterval()
412 ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval())
413 : this.getHeartbeatInterval()
414 }, not starting the heartbeat`
415 );
c0560973
JB
416 }
417 }
418
419 public restartHeartbeat(): void {
420 // Stop heartbeat
421 this.stopHeartbeat();
422 // Start heartbeat
423 this.startHeartbeat();
424 }
425
17ac262c
JB
426 public restartWebSocketPing(): void {
427 // Stop WebSocket ping
428 this.stopWebSocketPing();
429 // Start WebSocket ping
430 this.startWebSocketPing();
431 }
432
c0560973
JB
433 public startMeterValues(connectorId: number, interval: number): void {
434 if (connectorId === 0) {
e7aeea18
JB
435 logger.error(
436 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`
437 );
c0560973
JB
438 return;
439 }
734d790d 440 if (!this.getConnectorStatus(connectorId)) {
e7aeea18
JB
441 logger.error(
442 `${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`
443 );
c0560973
JB
444 return;
445 }
5e3cb728 446 if (this.getConnectorStatus(connectorId)?.transactionStarted === false) {
e7aeea18
JB
447 logger.error(
448 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`
449 );
c0560973 450 return;
e7aeea18 451 } else if (
5e3cb728 452 this.getConnectorStatus(connectorId)?.transactionStarted === true &&
d812bdcb 453 Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId)
e7aeea18
JB
454 ) {
455 logger.error(
456 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`
457 );
c0560973
JB
458 return;
459 }
460 if (interval > 0) {
6a8329b4
JB
461 this.getConnectorStatus(connectorId).transactionSetInterval = setInterval(() => {
462 // FIXME: Implement OCPP version agnostic helpers
463 const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue(
464 this,
465 connectorId,
466 this.getConnectorStatus(connectorId).transactionId,
467 interval
468 );
469 this.ocppRequestService
470 .requestHandler<MeterValuesRequest, MeterValuesResponse>(
08f130a0 471 this,
f22266fd
JB
472 RequestCommand.METER_VALUES,
473 {
474 connectorId,
72092cfc 475 transactionId: this.getConnectorStatus(connectorId)?.transactionId,
f22266fd
JB
476 meterValue: [meterValue],
477 }
6a8329b4 478 )
72092cfc 479 .catch((error) => {
6a8329b4
JB
480 logger.error(
481 `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
482 error
483 );
484 });
485 }, interval);
c0560973 486 } else {
e7aeea18
JB
487 logger.error(
488 `${this.logPrefix()} Charging station ${
489 StandardParametersKey.MeterValueSampleInterval
490 } configuration set to ${
491 interval ? Utils.formatDurationMilliSeconds(interval) : interval
492 }, not sending MeterValues`
493 );
c0560973
JB
494 }
495 }
496
497 public start(): void {
0d8852a5
JB
498 if (this.started === false) {
499 if (this.starting === false) {
500 this.starting = true;
ad774cec 501 if (this.getEnableStatistics() === true) {
551e477c 502 this.performanceStatistics?.start();
0d8852a5
JB
503 }
504 this.openWSConnection();
505 // Monitor charging station template file
506 this.templateFileWatcher = FileUtils.watchJsonFile(
0d8852a5 507 this.templateFile,
7164966d
JB
508 FileType.ChargingStationTemplate,
509 this.logPrefix(),
510 undefined,
0d8852a5 511 (event, filename): void => {
5a2a53cf 512 if (Utils.isNotEmptyString(filename) && event === 'change') {
0d8852a5
JB
513 try {
514 logger.debug(
515 `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
516 this.templateFile
517 } file have changed, reload`
518 );
519 this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
520 // Initialize
521 this.initialize();
522 // Restart the ATG
523 this.stopAutomaticTransactionGenerator();
524 if (
525 this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true
526 ) {
527 this.startAutomaticTransactionGenerator();
528 }
ad774cec 529 if (this.getEnableStatistics() === true) {
551e477c 530 this.performanceStatistics?.restart();
0d8852a5 531 } else {
551e477c 532 this.performanceStatistics?.stop();
0d8852a5
JB
533 }
534 // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
535 } catch (error) {
536 logger.error(
537 `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
538 error
539 );
950b1349 540 }
a95873d8 541 }
a95873d8 542 }
0d8852a5 543 );
56eb297e 544 this.started = true;
1895299d 545 parentPort?.postMessage(MessageChannelUtils.buildStartedMessage(this));
0d8852a5
JB
546 this.starting = false;
547 } else {
548 logger.warn(`${this.logPrefix()} Charging station is already starting...`);
549 }
950b1349 550 } else {
0d8852a5 551 logger.warn(`${this.logPrefix()} Charging station is already started...`);
950b1349 552 }
c0560973
JB
553 }
554
60ddad53 555 public async stop(reason?: StopTransactionReason): Promise<void> {
0d8852a5
JB
556 if (this.started === true) {
557 if (this.stopping === false) {
558 this.stopping = true;
559 await this.stopMessageSequence(reason);
0d8852a5 560 this.closeWSConnection();
ad774cec 561 if (this.getEnableStatistics() === true) {
551e477c 562 this.performanceStatistics?.stop();
0d8852a5
JB
563 }
564 this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
72092cfc 565 this.templateFileWatcher?.close();
0d8852a5 566 this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
1895299d 567 this.bootNotificationResponse = undefined;
0d8852a5 568 this.started = false;
1895299d 569 parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
0d8852a5
JB
570 this.stopping = false;
571 } else {
572 logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
c0560973 573 }
950b1349 574 } else {
0d8852a5 575 logger.warn(`${this.logPrefix()} Charging station is already stopped...`);
c0560973 576 }
c0560973
JB
577 }
578
60ddad53
JB
579 public async reset(reason?: StopTransactionReason): Promise<void> {
580 await this.stop(reason);
94ec7e96 581 await Utils.sleep(this.stationInfo.resetTime);
fa7bccf4 582 this.initialize();
94ec7e96
JB
583 this.start();
584 }
585
17ac262c
JB
586 public saveOcppConfiguration(): void {
587 if (this.getOcppPersistentConfiguration()) {
7c72977b 588 this.saveConfiguration();
e6895390
JB
589 }
590 }
591
a2653482
JB
592 public resetConnectorStatus(connectorId: number): void {
593 this.getConnectorStatus(connectorId).idTagLocalAuthorized = false;
594 this.getConnectorStatus(connectorId).idTagAuthorized = false;
595 this.getConnectorStatus(connectorId).transactionRemoteStarted = false;
734d790d 596 this.getConnectorStatus(connectorId).transactionStarted = false;
d812bdcb
JB
597 delete this.getConnectorStatus(connectorId)?.localAuthorizeIdTag;
598 delete this.getConnectorStatus(connectorId)?.authorizeIdTag;
599 delete this.getConnectorStatus(connectorId)?.transactionId;
600 delete this.getConnectorStatus(connectorId)?.transactionIdTag;
734d790d 601 this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
d812bdcb 602 delete this.getConnectorStatus(connectorId)?.transactionBeginMeterValue;
dd119a6b 603 this.stopMeterValues(connectorId);
1895299d 604 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
2e6f5966
JB
605 }
606
72092cfc 607 public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean | undefined {
17ac262c
JB
608 return ChargingStationConfigurationUtils.getConfigurationKey(
609 this,
610 StandardParametersKey.SupportedFeatureProfiles
72092cfc 611 )?.value?.includes(featureProfile);
68cb8b91
JB
612 }
613
8e242273
JB
614 public bufferMessage(message: string): void {
615 this.messageBuffer.add(message);
3ba2381e
JB
616 }
617
db2336d9
JB
618 public openWSConnection(
619 options: WsOptions = this.stationInfo?.wsOptions ?? {},
620 params: { closeOpened?: boolean; terminateOpened?: boolean } = {
621 closeOpened: false,
622 terminateOpened: false,
623 }
624 ): void {
625 options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
626 params.closeOpened = params?.closeOpened ?? false;
627 params.terminateOpened = params?.terminateOpened ?? false;
cbf9b878 628 if (this.started === false && this.starting === false) {
d1c6c833
JB
629 logger.warn(
630 `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} on stopped charging station`
631 );
632 return;
633 }
db2336d9
JB
634 if (
635 !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
636 !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
637 ) {
638 options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
639 }
640 if (params?.closeOpened) {
641 this.closeWSConnection();
642 }
643 if (params?.terminateOpened) {
644 this.terminateWSConnection();
645 }
cda96260 646 const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
db2336d9 647 let protocol: string;
edd13439 648 switch (ocppVersion) {
db2336d9 649 case OCPPVersion.VERSION_16:
edd13439
JB
650 case OCPPVersion.VERSION_20:
651 case OCPPVersion.VERSION_201:
44eb6026 652 protocol = `ocpp${ocppVersion}`;
db2336d9
JB
653 break;
654 default:
edd13439 655 this.handleUnsupportedVersion(ocppVersion);
db2336d9
JB
656 break;
657 }
658
56eb297e 659 if (this.isWebSocketConnectionOpened() === true) {
0a03f36c
JB
660 logger.warn(
661 `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`
662 );
663 return;
664 }
665
db2336d9 666 logger.info(
0a03f36c 667 `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
db2336d9
JB
668 );
669
670 this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
671
672 // Handle WebSocket message
673 this.wsConnection.on(
674 'message',
675 this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void
676 );
677 // Handle WebSocket error
678 this.wsConnection.on(
679 'error',
680 this.onError.bind(this) as (this: WebSocket, error: Error) => void
681 );
682 // Handle WebSocket close
683 this.wsConnection.on(
684 'close',
685 this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void
686 );
687 // Handle WebSocket open
688 this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void);
689 // Handle WebSocket ping
690 this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void);
691 // Handle WebSocket pong
692 this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void);
693 }
694
695 public closeWSConnection(): void {
56eb297e 696 if (this.isWebSocketConnectionOpened() === true) {
72092cfc 697 this.wsConnection?.close();
db2336d9
JB
698 this.wsConnection = null;
699 }
700 }
701
8f879946
JB
702 public startAutomaticTransactionGenerator(
703 connectorIds?: number[],
704 automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
705 ): void {
706 this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(
707 automaticTransactionGeneratorConfiguration ??
4f69be04 708 this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
8f879946
JB
709 this
710 );
53ac516c 711 if (Utils.isNotEmptyArray(connectorIds)) {
a5e9befc 712 for (const connectorId of connectorIds) {
551e477c 713 this.automaticTransactionGenerator?.startConnector(connectorId);
a5e9befc
JB
714 }
715 } else {
551e477c 716 this.automaticTransactionGenerator?.start();
4f69be04 717 }
1895299d 718 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
4f69be04
JB
719 }
720
a5e9befc 721 public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
53ac516c 722 if (Utils.isNotEmptyArray(connectorIds)) {
a5e9befc
JB
723 for (const connectorId of connectorIds) {
724 this.automaticTransactionGenerator?.stopConnector(connectorId);
725 }
726 } else {
727 this.automaticTransactionGenerator?.stop();
4f69be04 728 }
1895299d 729 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
4f69be04
JB
730 }
731
5e3cb728
JB
732 public async stopTransactionOnConnector(
733 connectorId: number,
734 reason = StopTransactionReason.NONE
735 ): Promise<StopTransactionResponse> {
72092cfc 736 const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
5e3cb728 737 if (
c7e8e0a2
JB
738 this.getBeginEndMeterValues() === true &&
739 this.getOcppStrictCompliance() === true &&
740 this.getOutOfOrderEndMeterValues() === false
5e3cb728
JB
741 ) {
742 // FIXME: Implement OCPP version agnostic helpers
743 const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
744 this,
745 connectorId,
746 this.getEnergyActiveImportRegisterByTransactionId(transactionId)
747 );
748 await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
749 this,
750 RequestCommand.METER_VALUES,
751 {
752 connectorId,
753 transactionId,
754 meterValue: [transactionEndMeterValue],
755 }
756 );
757 }
758 return this.ocppRequestService.requestHandler<StopTransactionRequest, StopTransactionResponse>(
759 this,
760 RequestCommand.STOP_TRANSACTION,
761 {
762 transactionId,
763 meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
5e3cb728
JB
764 reason,
765 }
766 );
767 }
768
f90c1757 769 private flushMessageBuffer(): void {
8e242273 770 if (this.messageBuffer.size > 0) {
72092cfc 771 this.messageBuffer.forEach((message) => {
1431af78
JB
772 let beginId: string;
773 let commandName: RequestCommand;
8ca6874c 774 const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
1431af78
JB
775 const isRequest = messageType === MessageType.CALL_MESSAGE;
776 if (isRequest) {
777 [, , commandName] = JSON.parse(message) as OutgoingRequest;
778 beginId = PerformanceStatistics.beginMeasure(commandName);
779 }
72092cfc 780 this.wsConnection?.send(message);
1431af78 781 isRequest && PerformanceStatistics.endMeasure(commandName, beginId);
8ca6874c
JB
782 logger.debug(
783 `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
784 messageType
785 )} payload sent: ${message}`
786 );
8e242273 787 this.messageBuffer.delete(message);
77f00f84
JB
788 });
789 }
790 }
791
1f5df42a
JB
792 private getSupervisionUrlOcppConfiguration(): boolean {
793 return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
12fc74d6
JB
794 }
795
e8e865ea
JB
796 private getSupervisionUrlOcppKey(): string {
797 return this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl;
798 }
799
72092cfc
JB
800 private getTemplateFromFile(): ChargingStationTemplate | undefined {
801 let template: ChargingStationTemplate;
5ad8570f 802 try {
57adbebc
JB
803 if (this.sharedLRUCache.hasChargingStationTemplate(this.stationInfo?.templateHash)) {
804 template = this.sharedLRUCache.getChargingStationTemplate(this.stationInfo.templateHash);
7c72977b
JB
805 } else {
806 const measureId = `${FileType.ChargingStationTemplate} read`;
807 const beginId = PerformanceStatistics.beginMeasure(measureId);
808 template = JSON.parse(
809 fs.readFileSync(this.templateFile, 'utf8')
810 ) as ChargingStationTemplate;
811 PerformanceStatistics.endMeasure(measureId, beginId);
812 template.templateHash = crypto
813 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
814 .update(JSON.stringify(template))
815 .digest('hex');
57adbebc 816 this.sharedLRUCache.setChargingStationTemplate(template);
7c72977b 817 }
5ad8570f 818 } catch (error) {
e7aeea18 819 FileUtils.handleFileException(
2484ac1e 820 this.templateFile,
7164966d
JB
821 FileType.ChargingStationTemplate,
822 error as NodeJS.ErrnoException,
823 this.logPrefix()
e7aeea18 824 );
5ad8570f 825 }
2484ac1e
JB
826 return template;
827 }
828
7a3a2ebb 829 private getStationInfoFromTemplate(): ChargingStationInfo {
72092cfc 830 const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
fa7bccf4 831 if (Utils.isNullOrUndefined(stationTemplate)) {
eaad6e5c 832 const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
ccb1d6e9
JB
833 logger.error(`${this.logPrefix()} ${errorMsg}`);
834 throw new BaseError(errorMsg);
94ec7e96 835 }
fa7bccf4 836 if (Utils.isEmptyObject(stationTemplate)) {
ccb1d6e9
JB
837 const errorMsg = `Empty charging station information from template file ${this.templateFile}`;
838 logger.error(`${this.logPrefix()} ${errorMsg}`);
839 throw new BaseError(errorMsg);
94ec7e96 840 }
2dcfe98e 841 // Deprecation template keys section
17ac262c 842 ChargingStationUtils.warnDeprecatedTemplateKey(
fa7bccf4 843 stationTemplate,
e7aeea18 844 'supervisionUrl',
17ac262c 845 this.templateFile,
ccb1d6e9 846 this.logPrefix(),
e7aeea18
JB
847 "Use 'supervisionUrls' instead"
848 );
17ac262c 849 ChargingStationUtils.convertDeprecatedTemplateKey(
fa7bccf4 850 stationTemplate,
17ac262c
JB
851 'supervisionUrl',
852 'supervisionUrls'
853 );
fa7bccf4
JB
854 const stationInfo: ChargingStationInfo =
855 ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
51c83d6f 856 stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
fa7bccf4
JB
857 stationInfo.chargingStationId = ChargingStationUtils.getChargingStationId(
858 this.index,
859 stationTemplate
860 );
72092cfc 861 stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16;
fa7bccf4 862 ChargingStationUtils.createSerialNumber(stationTemplate, stationInfo);
53ac516c 863 if (Utils.isNotEmptyArray(stationTemplate?.power)) {
551e477c 864 stationTemplate.power = stationTemplate.power as number[];
fa7bccf4 865 const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplate.power.length);
cc6e8ab5 866 stationInfo.maximumPower =
72092cfc 867 stationTemplate?.powerUnit === PowerUnits.KILO_WATT
fa7bccf4
JB
868 ? stationTemplate.power[powerArrayRandomIndex] * 1000
869 : stationTemplate.power[powerArrayRandomIndex];
5ad8570f 870 } else {
551e477c 871 stationTemplate.power = stationTemplate?.power as number;
cc6e8ab5 872 stationInfo.maximumPower =
72092cfc 873 stationTemplate?.powerUnit === PowerUnits.KILO_WATT
fa7bccf4
JB
874 ? stationTemplate.power * 1000
875 : stationTemplate.power;
876 }
3637ca2c 877 stationInfo.firmwareVersionPattern =
72092cfc 878 stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
3637ca2c 879 if (
5a2a53cf 880 Utils.isNotEmptyString(stationInfo.firmwareVersion) &&
3637ca2c
JB
881 new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false
882 ) {
883 logger.warn(
884 `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
885 this.templateFile
886 } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
887 );
888 }
598c886d 889 stationInfo.firmwareUpgrade = merge<FirmwareUpgrade>(
15748260 890 {
598c886d
JB
891 versionUpgrade: {
892 step: 1,
893 },
15748260
JB
894 reset: true,
895 },
72092cfc 896 stationTemplate?.firmwareUpgrade ?? {}
15748260 897 );
d812bdcb 898 stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
fa7bccf4 899 ? stationTemplate.resetTime * 1000
e7aeea18 900 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
c72f6634
JB
901 const configuredMaxConnectors =
902 ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate);
fa7bccf4
JB
903 ChargingStationUtils.checkConfiguredMaxConnectors(
904 configuredMaxConnectors,
905 this.templateFile,
fc040c43 906 this.logPrefix()
fa7bccf4
JB
907 );
908 const templateMaxConnectors =
909 ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
910 ChargingStationUtils.checkTemplateMaxConnectors(
911 templateMaxConnectors,
912 this.templateFile,
fc040c43 913 this.logPrefix()
fa7bccf4
JB
914 );
915 if (
916 configuredMaxConnectors >
917 (stationTemplate?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
918 !stationTemplate?.randomConnectors
919 ) {
920 logger.warn(
921 `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
922 this.templateFile
923 }, forcing random connector configurations affectation`
924 );
925 stationInfo.randomConnectors = true;
926 }
927 // Build connectors if needed (FIXME: should be factored out)
928 this.initializeConnectors(stationInfo, configuredMaxConnectors, templateMaxConnectors);
929 stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
930 ChargingStationUtils.createStationInfoHash(stationInfo);
9ac86a7e 931 return stationInfo;
5ad8570f
JB
932 }
933
551e477c
JB
934 private getStationInfoFromFile(): ChargingStationInfo | undefined {
935 let stationInfo: ChargingStationInfo | undefined;
fa7bccf4 936 this.getStationInfoPersistentConfiguration() &&
551e477c 937 (stationInfo = this.getConfigurationFromFile()?.stationInfo);
fa7bccf4 938 stationInfo && ChargingStationUtils.createStationInfoHash(stationInfo);
f765beaa 939 return stationInfo;
2484ac1e
JB
940 }
941
942 private getStationInfo(): ChargingStationInfo {
943 const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
551e477c 944 const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
aca53a1a 945 // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
f765beaa 946 if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
01efc60a
JB
947 if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
948 return this.stationInfo;
949 }
2484ac1e 950 return stationInfoFromFile;
f765beaa 951 }
fec4d204
JB
952 stationInfoFromFile &&
953 ChargingStationUtils.propagateSerialNumber(
954 this.getTemplateFromFile(),
955 stationInfoFromFile,
956 stationInfoFromTemplate
957 );
01efc60a 958 return stationInfoFromTemplate;
2484ac1e
JB
959 }
960
961 private saveStationInfo(): void {
ccb1d6e9 962 if (this.getStationInfoPersistentConfiguration()) {
7c72977b 963 this.saveConfiguration();
ccb1d6e9 964 }
2484ac1e
JB
965 }
966
e8e865ea 967 private getOcppPersistentConfiguration(): boolean {
ccb1d6e9
JB
968 return this.stationInfo?.ocppPersistentConfiguration ?? true;
969 }
970
971 private getStationInfoPersistentConfiguration(): boolean {
972 return this.stationInfo?.stationInfoPersistentConfiguration ?? true;
e8e865ea
JB
973 }
974
c0560973 975 private handleUnsupportedVersion(version: OCPPVersion) {
fc040c43
JB
976 const errMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
977 logger.error(`${this.logPrefix()} ${errMsg}`);
6c8f5d90 978 throw new BaseError(errMsg);
c0560973
JB
979 }
980
2484ac1e 981 private initialize(): void {
fa7bccf4 982 this.configurationFile = path.join(
ee5f26a2 983 path.dirname(this.templateFile.replace('station-templates', 'configurations')),
44eb6026 984 `${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
0642c3d2 985 );
b44b779a
JB
986 this.stationInfo = this.getStationInfo();
987 this.saveStationInfo();
7a3a2ebb 988 // Avoid duplication of connectors related information in RAM
94ec7e96 989 this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
fa7bccf4 990 this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
ad774cec 991 if (this.getEnableStatistics() === true) {
0642c3d2 992 this.performanceStatistics = PerformanceStatistics.getInstance(
51c83d6f 993 this.stationInfo.hashId,
0642c3d2 994 this.stationInfo.chargingStationId,
fa7bccf4 995 this.configuredSupervisionUrl
0642c3d2
JB
996 );
997 }
fa7bccf4
JB
998 this.bootNotificationRequest = ChargingStationUtils.createBootNotificationRequest(
999 this.stationInfo
1000 );
fa7bccf4
JB
1001 this.powerDivider = this.getPowerDivider();
1002 // OCPP configuration
1003 this.ocppConfiguration = this.getOcppConfiguration();
1004 this.initializeOcppConfiguration();
cda96260
JB
1005 const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
1006 switch (ocppVersion) {
c0560973 1007 case OCPPVersion.VERSION_16:
e7aeea18 1008 this.ocppIncomingRequestService =
08f130a0 1009 OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>();
e7aeea18 1010 this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
08f130a0 1011 OCPP16ResponseService.getInstance<OCPP16ResponseService>()
e7aeea18 1012 );
c0560973 1013 break;
edd13439
JB
1014 case OCPPVersion.VERSION_20:
1015 case OCPPVersion.VERSION_201:
1016 this.ocppIncomingRequestService =
1017 OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
1018 this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
1019 OCPP20ResponseService.getInstance<OCPP20ResponseService>()
1020 );
1021 break;
c0560973 1022 default:
cda96260 1023 this.handleUnsupportedVersion(ocppVersion);
c0560973
JB
1024 break;
1025 }
b7f9e41d 1026 if (this.stationInfo?.autoRegister === true) {
47e22477 1027 this.bootNotificationResponse = {
d270cc87 1028 currentTime: new Date(),
47e22477 1029 interval: this.getHeartbeatInterval() / 1000,
d270cc87 1030 status: RegistrationStatusEnumType.ACCEPTED,
47e22477
JB
1031 };
1032 }
3637ca2c
JB
1033 if (
1034 this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
5a2a53cf
JB
1035 Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
1036 Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
3637ca2c 1037 ) {
d812bdcb 1038 const patternGroup: number | undefined =
15748260 1039 this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
d812bdcb 1040 this.stationInfo.firmwareVersion?.split('.').length;
72092cfc
JB
1041 const match = this.stationInfo?.firmwareVersion
1042 ?.match(new RegExp(this.stationInfo.firmwareVersionPattern))
1043 ?.slice(1, patternGroup + 1);
3637ca2c 1044 const patchLevelIndex = match.length - 1;
5d280aae 1045 match[patchLevelIndex] = (
07c52a72
JB
1046 Utils.convertToInt(match[patchLevelIndex]) +
1047 this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
5d280aae 1048 ).toString();
72092cfc 1049 this.stationInfo.firmwareVersion = match?.join('.');
3637ca2c 1050 }
147d0e0f
JB
1051 }
1052
2484ac1e 1053 private initializeOcppConfiguration(): void {
17ac262c
JB
1054 if (
1055 !ChargingStationConfigurationUtils.getConfigurationKey(
1056 this,
1057 StandardParametersKey.HeartbeatInterval
1058 )
1059 ) {
1060 ChargingStationConfigurationUtils.addConfigurationKey(
1061 this,
1062 StandardParametersKey.HeartbeatInterval,
1063 '0'
1064 );
f0f65a62 1065 }
17ac262c
JB
1066 if (
1067 !ChargingStationConfigurationUtils.getConfigurationKey(
1068 this,
1069 StandardParametersKey.HeartBeatInterval
1070 )
1071 ) {
1072 ChargingStationConfigurationUtils.addConfigurationKey(
1073 this,
1074 StandardParametersKey.HeartBeatInterval,
1075 '0',
1076 { visible: false }
1077 );
f0f65a62 1078 }
e7aeea18
JB
1079 if (
1080 this.getSupervisionUrlOcppConfiguration() &&
17ac262c 1081 !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
e7aeea18 1082 ) {
17ac262c
JB
1083 ChargingStationConfigurationUtils.addConfigurationKey(
1084 this,
a59737e3 1085 this.getSupervisionUrlOcppKey(),
fa7bccf4 1086 this.configuredSupervisionUrl.href,
e7aeea18
JB
1087 { reboot: true }
1088 );
e6895390
JB
1089 } else if (
1090 !this.getSupervisionUrlOcppConfiguration() &&
17ac262c 1091 ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
e6895390 1092 ) {
17ac262c
JB
1093 ChargingStationConfigurationUtils.deleteConfigurationKey(
1094 this,
1095 this.getSupervisionUrlOcppKey(),
1096 { save: false }
1097 );
12fc74d6 1098 }
cc6e8ab5 1099 if (
5a2a53cf 1100 Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
17ac262c
JB
1101 !ChargingStationConfigurationUtils.getConfigurationKey(
1102 this,
1103 this.stationInfo.amperageLimitationOcppKey
1104 )
cc6e8ab5 1105 ) {
17ac262c
JB
1106 ChargingStationConfigurationUtils.addConfigurationKey(
1107 this,
cc6e8ab5 1108 this.stationInfo.amperageLimitationOcppKey,
17ac262c
JB
1109 (
1110 this.stationInfo.maximumAmperage *
1111 ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
1112 ).toString()
cc6e8ab5
JB
1113 );
1114 }
17ac262c
JB
1115 if (
1116 !ChargingStationConfigurationUtils.getConfigurationKey(
1117 this,
1118 StandardParametersKey.SupportedFeatureProfiles
1119 )
1120 ) {
1121 ChargingStationConfigurationUtils.addConfigurationKey(
1122 this,
e7aeea18 1123 StandardParametersKey.SupportedFeatureProfiles,
b22787b4 1124 `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
e7aeea18
JB
1125 );
1126 }
17ac262c
JB
1127 ChargingStationConfigurationUtils.addConfigurationKey(
1128 this,
e7aeea18
JB
1129 StandardParametersKey.NumberOfConnectors,
1130 this.getNumberOfConnectors().toString(),
a95873d8
JB
1131 { readonly: true },
1132 { overwrite: true }
e7aeea18 1133 );
17ac262c
JB
1134 if (
1135 !ChargingStationConfigurationUtils.getConfigurationKey(
1136 this,
1137 StandardParametersKey.MeterValuesSampledData
1138 )
1139 ) {
1140 ChargingStationConfigurationUtils.addConfigurationKey(
1141 this,
e7aeea18
JB
1142 StandardParametersKey.MeterValuesSampledData,
1143 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1144 );
7abfea5f 1145 }
17ac262c
JB
1146 if (
1147 !ChargingStationConfigurationUtils.getConfigurationKey(
1148 this,
1149 StandardParametersKey.ConnectorPhaseRotation
1150 )
1151 ) {
7e1dc878 1152 const connectorPhaseRotation = [];
734d790d 1153 for (const connectorId of this.connectors.keys()) {
7e1dc878 1154 // AC/DC
734d790d
JB
1155 if (connectorId === 0 && this.getNumberOfPhases() === 0) {
1156 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
1157 } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
1158 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
e7aeea18 1159 // AC
734d790d
JB
1160 } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
1161 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
1162 } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
1163 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
7e1dc878
JB
1164 }
1165 }
17ac262c
JB
1166 ChargingStationConfigurationUtils.addConfigurationKey(
1167 this,
e7aeea18
JB
1168 StandardParametersKey.ConnectorPhaseRotation,
1169 connectorPhaseRotation.toString()
1170 );
7e1dc878 1171 }
e7aeea18 1172 if (
17ac262c
JB
1173 !ChargingStationConfigurationUtils.getConfigurationKey(
1174 this,
1175 StandardParametersKey.AuthorizeRemoteTxRequests
e7aeea18
JB
1176 )
1177 ) {
17ac262c
JB
1178 ChargingStationConfigurationUtils.addConfigurationKey(
1179 this,
1180 StandardParametersKey.AuthorizeRemoteTxRequests,
1181 'true'
1182 );
36f6a92e 1183 }
17ac262c
JB
1184 if (
1185 !ChargingStationConfigurationUtils.getConfigurationKey(
1186 this,
1187 StandardParametersKey.LocalAuthListEnabled
1188 ) &&
1189 ChargingStationConfigurationUtils.getConfigurationKey(
1190 this,
1191 StandardParametersKey.SupportedFeatureProfiles
72092cfc 1192 )?.value?.includes(SupportedFeatureProfiles.LocalAuthListManagement)
17ac262c
JB
1193 ) {
1194 ChargingStationConfigurationUtils.addConfigurationKey(
1195 this,
1196 StandardParametersKey.LocalAuthListEnabled,
1197 'false'
1198 );
1199 }
1200 if (
1201 !ChargingStationConfigurationUtils.getConfigurationKey(
1202 this,
1203 StandardParametersKey.ConnectionTimeOut
1204 )
1205 ) {
1206 ChargingStationConfigurationUtils.addConfigurationKey(
1207 this,
e7aeea18
JB
1208 StandardParametersKey.ConnectionTimeOut,
1209 Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
1210 );
8bce55bf 1211 }
2484ac1e 1212 this.saveOcppConfiguration();
073bd098
JB
1213 }
1214
3d25cc86
JB
1215 private initializeConnectors(
1216 stationInfo: ChargingStationInfo,
fa7bccf4 1217 configuredMaxConnectors: number,
3d25cc86
JB
1218 templateMaxConnectors: number
1219 ): void {
1220 if (!stationInfo?.Connectors && this.connectors.size === 0) {
fc040c43
JB
1221 const logMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
1222 logger.error(`${this.logPrefix()} ${logMsg}`);
3d25cc86
JB
1223 throw new BaseError(logMsg);
1224 }
1225 if (!stationInfo?.Connectors[0]) {
1226 logger.warn(
1227 `${this.logPrefix()} Charging station information from template ${
1228 this.templateFile
1229 } with no connector Id 0 configuration`
1230 );
1231 }
1232 if (stationInfo?.Connectors) {
1233 const connectorsConfigHash = crypto
1234 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
14ecae6a 1235 .update(`${JSON.stringify(stationInfo?.Connectors)}${configuredMaxConnectors.toString()}`)
3d25cc86
JB
1236 .digest('hex');
1237 const connectorsConfigChanged =
1238 this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash;
1239 if (this.connectors?.size === 0 || connectorsConfigChanged) {
1240 connectorsConfigChanged && this.connectors.clear();
1241 this.connectorsConfigurationHash = connectorsConfigHash;
1242 // Add connector Id 0
1243 let lastConnector = '0';
1244 for (lastConnector in stationInfo?.Connectors) {
56eb297e 1245 const connectorStatus = stationInfo?.Connectors[lastConnector];
3d25cc86
JB
1246 const lastConnectorId = Utils.convertToInt(lastConnector);
1247 if (
1248 lastConnectorId === 0 &&
bb83b5ed 1249 this.getUseConnectorId0(stationInfo) === true &&
56eb297e 1250 connectorStatus
3d25cc86 1251 ) {
56eb297e 1252 this.checkStationInfoConnectorStatus(lastConnectorId, connectorStatus);
3d25cc86
JB
1253 this.connectors.set(
1254 lastConnectorId,
56eb297e 1255 Utils.cloneObject<ConnectorStatus>(connectorStatus)
3d25cc86
JB
1256 );
1257 this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.OPERATIVE;
1258 if (Utils.isUndefined(this.getConnectorStatus(lastConnectorId)?.chargingProfiles)) {
1259 this.getConnectorStatus(lastConnectorId).chargingProfiles = [];
1260 }
1261 }
1262 }
1263 // Generate all connectors
1264 if ((stationInfo?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) > 0) {
fa7bccf4 1265 for (let index = 1; index <= configuredMaxConnectors; index++) {
ccb1d6e9 1266 const randConnectorId = stationInfo?.randomConnectors
3d25cc86
JB
1267 ? Utils.getRandomInteger(Utils.convertToInt(lastConnector), 1)
1268 : index;
56eb297e
JB
1269 const connectorStatus = stationInfo?.Connectors[randConnectorId.toString()];
1270 this.checkStationInfoConnectorStatus(randConnectorId, connectorStatus);
1271 this.connectors.set(index, Utils.cloneObject<ConnectorStatus>(connectorStatus));
3d25cc86
JB
1272 this.getConnectorStatus(index).availability = AvailabilityType.OPERATIVE;
1273 if (Utils.isUndefined(this.getConnectorStatus(index)?.chargingProfiles)) {
1274 this.getConnectorStatus(index).chargingProfiles = [];
1275 }
1276 }
1277 }
1278 }
1279 } else {
1280 logger.warn(
1281 `${this.logPrefix()} Charging station information from template ${
1282 this.templateFile
1283 } with no connectors configuration defined, using already defined connectors`
1284 );
1285 }
1286 // Initialize transaction attributes on connectors
1287 for (const connectorId of this.connectors.keys()) {
72092cfc 1288 if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
f5b51071
JB
1289 logger.warn(
1290 `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
72092cfc 1291 this.getConnectorStatus(connectorId)?.transactionId
f5b51071
JB
1292 }`
1293 );
1294 }
1984f194
JB
1295 if (
1296 connectorId > 0 &&
72092cfc
JB
1297 (this.getConnectorStatus(connectorId)?.transactionStarted === undefined ||
1298 this.getConnectorStatus(connectorId)?.transactionStarted === null)
1984f194 1299 ) {
3d25cc86
JB
1300 this.initializeConnectorStatus(connectorId);
1301 }
1302 }
1303 }
1304
56eb297e
JB
1305 private checkStationInfoConnectorStatus(
1306 connectorId: number,
1307 connectorStatus: ConnectorStatus
1308 ): void {
1309 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
1310 logger.warn(
1311 `${this.logPrefix()} Charging station information from template ${
1312 this.templateFile
1313 } with connector ${connectorId} status configuration defined, undefine it`
1314 );
1315 connectorStatus.status = undefined;
1316 }
1317 }
1318
551e477c
JB
1319 private getConfigurationFromFile(): ChargingStationConfiguration | undefined {
1320 let configuration: ChargingStationConfiguration | undefined;
2484ac1e 1321 if (this.configurationFile && fs.existsSync(this.configurationFile)) {
073bd098 1322 try {
57adbebc
JB
1323 if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
1324 configuration = this.sharedLRUCache.getChargingStationConfiguration(
1325 this.configurationFileHash
1326 );
7c72977b
JB
1327 } else {
1328 const measureId = `${FileType.ChargingStationConfiguration} read`;
1329 const beginId = PerformanceStatistics.beginMeasure(measureId);
1330 configuration = JSON.parse(
1331 fs.readFileSync(this.configurationFile, 'utf8')
1332 ) as ChargingStationConfiguration;
1333 PerformanceStatistics.endMeasure(measureId, beginId);
1334 this.configurationFileHash = configuration.configurationHash;
57adbebc 1335 this.sharedLRUCache.setChargingStationConfiguration(configuration);
7c72977b 1336 }
073bd098
JB
1337 } catch (error) {
1338 FileUtils.handleFileException(
073bd098 1339 this.configurationFile,
7164966d
JB
1340 FileType.ChargingStationConfiguration,
1341 error as NodeJS.ErrnoException,
1342 this.logPrefix()
073bd098
JB
1343 );
1344 }
1345 }
1346 return configuration;
1347 }
1348
7c72977b 1349 private saveConfiguration(): void {
2484ac1e
JB
1350 if (this.configurationFile) {
1351 try {
2484ac1e
JB
1352 if (!fs.existsSync(path.dirname(this.configurationFile))) {
1353 fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
073bd098 1354 }
ccb1d6e9
JB
1355 const configurationData: ChargingStationConfiguration =
1356 this.getConfigurationFromFile() ?? {};
7c72977b
JB
1357 this.ocppConfiguration?.configurationKey &&
1358 (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
1359 this.stationInfo && (configurationData.stationInfo = this.stationInfo);
1360 delete configurationData.configurationHash;
1361 const configurationHash = crypto
1362 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
1363 .update(JSON.stringify(configurationData))
1364 .digest('hex');
1365 if (this.configurationFileHash !== configurationHash) {
1366 configurationData.configurationHash = configurationHash;
1367 const measureId = `${FileType.ChargingStationConfiguration} write`;
1368 const beginId = PerformanceStatistics.beginMeasure(measureId);
1369 const fileDescriptor = fs.openSync(this.configurationFile, 'w');
1370 fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
1371 fs.closeSync(fileDescriptor);
1372 PerformanceStatistics.endMeasure(measureId, beginId);
57adbebc 1373 this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
7c72977b 1374 this.configurationFileHash = configurationHash;
57adbebc 1375 this.sharedLRUCache.setChargingStationConfiguration(configurationData);
7c72977b
JB
1376 } else {
1377 logger.debug(
1378 `${this.logPrefix()} Not saving unchanged charging station configuration file ${
1379 this.configurationFile
1380 }`
1381 );
2484ac1e 1382 }
2484ac1e
JB
1383 } catch (error) {
1384 FileUtils.handleFileException(
2484ac1e 1385 this.configurationFile,
7164966d
JB
1386 FileType.ChargingStationConfiguration,
1387 error as NodeJS.ErrnoException,
1388 this.logPrefix()
073bd098
JB
1389 );
1390 }
2484ac1e
JB
1391 } else {
1392 logger.error(
01efc60a 1393 `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
2484ac1e 1394 );
073bd098
JB
1395 }
1396 }
1397
551e477c
JB
1398 private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration | undefined {
1399 return this.getTemplateFromFile()?.Configuration;
2484ac1e
JB
1400 }
1401
551e477c
JB
1402 private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
1403 let configuration: ChargingStationConfiguration | undefined;
23290150 1404 if (this.getOcppPersistentConfiguration() === true) {
7a3a2ebb
JB
1405 const configurationFromFile = this.getConfigurationFromFile();
1406 configuration = configurationFromFile?.configurationKey && configurationFromFile;
073bd098 1407 }
2484ac1e 1408 configuration && delete configuration.stationInfo;
073bd098 1409 return configuration;
7dde0b73
JB
1410 }
1411
551e477c
JB
1412 private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined {
1413 let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
72092cfc 1414 this.getOcppConfigurationFromFile();
2484ac1e
JB
1415 if (!ocppConfiguration) {
1416 ocppConfiguration = this.getOcppConfigurationFromTemplate();
1417 }
1418 return ocppConfiguration;
1419 }
1420
c0560973 1421 private async onOpen(): Promise<void> {
56eb297e 1422 if (this.isWebSocketConnectionOpened() === true) {
5144f4d1
JB
1423 logger.info(
1424 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`
1425 );
ed6cfcff 1426 if (this.isRegistered() === false) {
5144f4d1
JB
1427 // Send BootNotification
1428 let registrationRetryCount = 0;
1429 do {
f7f98c68 1430 this.bootNotificationResponse = await this.ocppRequestService.requestHandler<
5144f4d1
JB
1431 BootNotificationRequest,
1432 BootNotificationResponse
8bfbc743
JB
1433 >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
1434 skipBufferingOnError: true,
1435 });
ed6cfcff 1436 if (this.isRegistered() === false) {
5144f4d1
JB
1437 this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
1438 await Utils.sleep(
1895299d 1439 this?.bootNotificationResponse?.interval
5144f4d1
JB
1440 ? this.bootNotificationResponse.interval * 1000
1441 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
1442 );
1443 }
1444 } while (
ed6cfcff 1445 this.isRegistered() === false &&
5144f4d1
JB
1446 (registrationRetryCount <= this.getRegistrationMaxRetries() ||
1447 this.getRegistrationMaxRetries() === -1)
1448 );
1449 }
ed6cfcff 1450 if (this.isRegistered() === true) {
23290150 1451 if (this.isInAcceptedState() === true) {
94bb24d5 1452 await this.startMessageSequence();
c0560973 1453 }
5144f4d1
JB
1454 } else {
1455 logger.error(
1456 `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`
1457 );
caad9d6b 1458 }
5144f4d1 1459 this.wsConnectionRestarted = false;
aa428a31 1460 this.autoReconnectRetryCount = 0;
1895299d 1461 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
2e6f5966 1462 } else {
5144f4d1
JB
1463 logger.warn(
1464 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
e7aeea18 1465 );
2e6f5966 1466 }
2e6f5966
JB
1467 }
1468
ef7d8c21 1469 private async onClose(code: number, reason: Buffer): Promise<void> {
d09085e9 1470 switch (code) {
6c65a295
JB
1471 // Normal close
1472 case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
c0560973 1473 case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
e7aeea18 1474 logger.info(
5e3cb728 1475 `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
e7aeea18 1476 code
ef7d8c21 1477 )}' and reason '${reason.toString()}'`
e7aeea18 1478 );
c0560973
JB
1479 this.autoReconnectRetryCount = 0;
1480 break;
6c65a295
JB
1481 // Abnormal close
1482 default:
e7aeea18 1483 logger.error(
5e3cb728 1484 `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
e7aeea18 1485 code
ef7d8c21 1486 )}' and reason '${reason.toString()}'`
e7aeea18 1487 );
56eb297e 1488 this.started === true && (await this.reconnect());
c0560973
JB
1489 break;
1490 }
1895299d 1491 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
2e6f5966
JB
1492 }
1493
ef7d8c21 1494 private async onMessage(data: RawData): Promise<void> {
b3ec7bc1
JB
1495 let messageType: number;
1496 let messageId: string;
1497 let commandName: IncomingRequestCommand;
1498 let commandPayload: JsonType;
1499 let errorType: ErrorType;
1500 let errorMessage: string;
1501 let errorDetails: JsonType;
d900c8d7
JB
1502 let responseCallback: ResponseCallback;
1503 let errorCallback: ErrorCallback;
32b02249 1504 let requestCommandName: RequestCommand | IncomingRequestCommand;
b3ec7bc1 1505 let requestPayload: JsonType;
32b02249 1506 let cachedRequest: CachedRequest;
c0560973
JB
1507 let errMsg: string;
1508 try {
b3ec7bc1 1509 const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
53e5fd67 1510 if (Array.isArray(request) === true) {
9934652c 1511 [messageType, messageId] = request;
b3ec7bc1
JB
1512 // Check the type of message
1513 switch (messageType) {
1514 // Incoming Message
1515 case MessageType.CALL_MESSAGE:
9934652c 1516 [, , commandName, commandPayload] = request as IncomingRequest;
0638ddd2 1517 if (this.getEnableStatistics() === true) {
551e477c 1518 this.performanceStatistics?.addRequestStatistic(commandName, messageType);
b3ec7bc1
JB
1519 }
1520 logger.debug(
1521 `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
1522 request
1523 )}`
1524 );
1525 // Process the message
1526 await this.ocppIncomingRequestService.incomingRequestHandler(
08f130a0 1527 this,
b3ec7bc1
JB
1528 messageId,
1529 commandName,
1530 commandPayload
1531 );
1532 break;
1533 // Outcome Message
1534 case MessageType.CALL_RESULT_MESSAGE:
9934652c 1535 [, , commandPayload] = request as Response;
ba7965c4 1536 if (this.requests.has(messageId) === false) {
a2d1c0f1
JB
1537 // Error
1538 throw new OCPPError(
1539 ErrorType.INTERNAL_ERROR,
1540 `Response for unknown message id ${messageId}`,
72092cfc 1541 undefined,
a2d1c0f1
JB
1542 commandPayload
1543 );
1544 }
b3ec7bc1
JB
1545 // Respond
1546 cachedRequest = this.requests.get(messageId);
53e5fd67 1547 if (Array.isArray(cachedRequest) === true) {
53e07f94 1548 [responseCallback, errorCallback, requestCommandName, requestPayload] = cachedRequest;
b3ec7bc1
JB
1549 } else {
1550 throw new OCPPError(
1551 ErrorType.PROTOCOL_ERROR,
53e5fd67 1552 `Cached request for message id ${messageId} response is not an array`,
72092cfc 1553 undefined,
c2bc716f 1554 cachedRequest as unknown as JsonType
b3ec7bc1
JB
1555 );
1556 }
1557 logger.debug(
7ec6c5c9 1558 `${this.logPrefix()} << Command '${
2a232a18 1559 requestCommandName ?? Constants.UNKNOWN_COMMAND
7ec6c5c9 1560 }' received response payload: ${JSON.stringify(request)}`
b3ec7bc1 1561 );
a2d1c0f1
JB
1562 responseCallback(commandPayload, requestPayload);
1563 break;
1564 // Error Message
1565 case MessageType.CALL_ERROR_MESSAGE:
1566 [, , errorType, errorMessage, errorDetails] = request as ErrorResponse;
ba7965c4 1567 if (this.requests.has(messageId) === false) {
b3ec7bc1
JB
1568 // Error
1569 throw new OCPPError(
1570 ErrorType.INTERNAL_ERROR,
a2d1c0f1 1571 `Error response for unknown message id ${messageId}`,
72092cfc 1572 undefined,
a2d1c0f1 1573 { errorType, errorMessage, errorDetails }
b3ec7bc1
JB
1574 );
1575 }
b3ec7bc1 1576 cachedRequest = this.requests.get(messageId);
53e5fd67 1577 if (Array.isArray(cachedRequest) === true) {
a2d1c0f1 1578 [, errorCallback, requestCommandName] = cachedRequest;
b3ec7bc1
JB
1579 } else {
1580 throw new OCPPError(
1581 ErrorType.PROTOCOL_ERROR,
53e5fd67 1582 `Cached request for message id ${messageId} error response is not an array`,
72092cfc 1583 undefined,
c2bc716f 1584 cachedRequest as unknown as JsonType
b3ec7bc1
JB
1585 );
1586 }
1587 logger.debug(
7ec6c5c9 1588 `${this.logPrefix()} << Command '${
2a232a18 1589 requestCommandName ?? Constants.UNKNOWN_COMMAND
2cc5d5ec 1590 }' received error response payload: ${JSON.stringify(request)}`
b3ec7bc1 1591 );
a2d1c0f1 1592 errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
b3ec7bc1
JB
1593 break;
1594 // Error
1595 default:
1596 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
fc040c43
JB
1597 errMsg = `Wrong message type ${messageType}`;
1598 logger.error(`${this.logPrefix()} ${errMsg}`);
b3ec7bc1
JB
1599 throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
1600 }
1895299d 1601 parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
47e22477 1602 } else {
53e5fd67 1603 throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
ba7965c4 1604 request,
ac54a9bb 1605 });
47e22477 1606 }
c0560973
JB
1607 } catch (error) {
1608 // Log
e7aeea18 1609 logger.error(
91a4f151 1610 `${this.logPrefix()} Incoming OCPP command '${
2a232a18 1611 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
9c5d9fa4
JB
1612 }' message '${data.toString()}'${
1613 messageType !== MessageType.CALL_MESSAGE
1614 ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
1615 : ''
1616 } processing error:`,
e7aeea18
JB
1617 error
1618 );
ba7965c4 1619 if (error instanceof OCPPError === false) {
247659af 1620 logger.warn(
91a4f151 1621 `${this.logPrefix()} Error thrown at incoming OCPP command '${
2a232a18 1622 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
fc040c43 1623 }' message '${data.toString()}' handling is not an OCPPError:`,
247659af
JB
1624 error
1625 );
1626 }
13701f69
JB
1627 switch (messageType) {
1628 case MessageType.CALL_MESSAGE:
1629 // Send error
1630 await this.ocppRequestService.sendError(
1631 this,
1632 messageId,
1633 error as OCPPError,
1634 commandName ?? requestCommandName ?? null
1635 );
1636 break;
1637 case MessageType.CALL_RESULT_MESSAGE:
1638 case MessageType.CALL_ERROR_MESSAGE:
1639 if (errorCallback) {
1640 // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
1641 errorCallback(error as OCPPError, false);
1642 } else {
1643 // Remove the request from the cache in case of error at response handling
1644 this.requests.delete(messageId);
1645 }
de4cb8b6 1646 break;
ba7965c4 1647 }
c0560973 1648 }
2328be1e
JB
1649 }
1650
c0560973 1651 private onPing(): void {
44eb6026 1652 logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`);
c0560973
JB
1653 }
1654
1655 private onPong(): void {
44eb6026 1656 logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`);
c0560973
JB
1657 }
1658
9534e74e 1659 private onError(error: WSError): void {
bcc9c3c0 1660 this.closeWSConnection();
44eb6026 1661 logger.error(`${this.logPrefix()} WebSocket error:`, error);
c0560973
JB
1662 }
1663
18bf8274 1664 private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
95bdbf12 1665 if (this.getMeteringPerTransaction() === true) {
07989fad 1666 return (
18bf8274 1667 (rounded === true
07989fad
JB
1668 ? Math.round(connectorStatus?.transactionEnergyActiveImportRegisterValue)
1669 : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
1670 );
1671 }
1672 return (
18bf8274 1673 (rounded === true
07989fad
JB
1674 ? Math.round(connectorStatus?.energyActiveImportRegisterValue)
1675 : connectorStatus?.energyActiveImportRegisterValue) ?? 0
1676 );
1677 }
1678
bb83b5ed 1679 private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean {
fa7bccf4 1680 const localStationInfo = stationInfo ?? this.stationInfo;
a14885a3 1681 return localStationInfo?.useConnectorId0 ?? true;
8bce55bf
JB
1682 }
1683
60ddad53
JB
1684 private getNumberOfRunningTransactions(): number {
1685 let trxCount = 0;
1686 for (const connectorId of this.connectors.keys()) {
1687 if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
1688 trxCount++;
1689 }
1690 }
1691 return trxCount;
1692 }
1693
1694 private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
1695 for (const connectorId of this.connectors.keys()) {
1696 if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
1697 await this.stopTransactionOnConnector(connectorId, reason);
1698 }
1699 }
1700 }
1701
1f761b9a 1702 // 0 for disabling
c72f6634 1703 private getConnectionTimeout(): number {
17ac262c
JB
1704 if (
1705 ChargingStationConfigurationUtils.getConfigurationKey(
1706 this,
1707 StandardParametersKey.ConnectionTimeOut
1708 )
1709 ) {
e7aeea18 1710 return (
17ac262c
JB
1711 parseInt(
1712 ChargingStationConfigurationUtils.getConfigurationKey(
1713 this,
1714 StandardParametersKey.ConnectionTimeOut
1715 ).value
1716 ) ?? Constants.DEFAULT_CONNECTION_TIMEOUT
e7aeea18 1717 );
291cb255 1718 }
291cb255 1719 return Constants.DEFAULT_CONNECTION_TIMEOUT;
3574dfd3
JB
1720 }
1721
1f761b9a 1722 // -1 for unlimited, 0 for disabling
72092cfc 1723 private getAutoReconnectMaxRetries(): number | undefined {
ad2f27c3
JB
1724 if (!Utils.isUndefined(this.stationInfo.autoReconnectMaxRetries)) {
1725 return this.stationInfo.autoReconnectMaxRetries;
3574dfd3
JB
1726 }
1727 if (!Utils.isUndefined(Configuration.getAutoReconnectMaxRetries())) {
1728 return Configuration.getAutoReconnectMaxRetries();
1729 }
1730 return -1;
1731 }
1732
ec977daf 1733 // 0 for disabling
72092cfc 1734 private getRegistrationMaxRetries(): number | undefined {
ad2f27c3
JB
1735 if (!Utils.isUndefined(this.stationInfo.registrationMaxRetries)) {
1736 return this.stationInfo.registrationMaxRetries;
32a1eb7a
JB
1737 }
1738 return -1;
1739 }
1740
c0560973
JB
1741 private getPowerDivider(): number {
1742 let powerDivider = this.getNumberOfConnectors();
fa7bccf4 1743 if (this.stationInfo?.powerSharedByConnectors) {
c0560973 1744 powerDivider = this.getNumberOfRunningTransactions();
6ecb15e4
JB
1745 }
1746 return powerDivider;
1747 }
1748
fa7bccf4
JB
1749 private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined {
1750 const maximumPower = this.getMaximumPower(stationInfo);
1751 switch (this.getCurrentOutType(stationInfo)) {
cc6e8ab5
JB
1752 case CurrentType.AC:
1753 return ACElectricUtils.amperagePerPhaseFromPower(
fa7bccf4 1754 this.getNumberOfPhases(stationInfo),
ad8537a7 1755 maximumPower / this.getNumberOfConnectors(),
fa7bccf4 1756 this.getVoltageOut(stationInfo)
cc6e8ab5
JB
1757 );
1758 case CurrentType.DC:
fa7bccf4 1759 return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo));
cc6e8ab5
JB
1760 }
1761 }
1762
cc6e8ab5
JB
1763 private getAmperageLimitation(): number | undefined {
1764 if (
5a2a53cf 1765 Utils.isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
17ac262c
JB
1766 ChargingStationConfigurationUtils.getConfigurationKey(
1767 this,
1768 this.stationInfo.amperageLimitationOcppKey
1769 )
cc6e8ab5
JB
1770 ) {
1771 return (
1772 Utils.convertToInt(
17ac262c
JB
1773 ChargingStationConfigurationUtils.getConfigurationKey(
1774 this,
1775 this.stationInfo.amperageLimitationOcppKey
72092cfc 1776 )?.value
17ac262c 1777 ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
cc6e8ab5
JB
1778 );
1779 }
1780 }
1781
c0560973 1782 private async startMessageSequence(): Promise<void> {
b7f9e41d 1783 if (this.stationInfo?.autoRegister === true) {
f7f98c68 1784 await this.ocppRequestService.requestHandler<
ef6fa3fb
JB
1785 BootNotificationRequest,
1786 BootNotificationResponse
8bfbc743
JB
1787 >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
1788 skipBufferingOnError: true,
1789 });
6114e6f1 1790 }
136c90ba 1791 // Start WebSocket ping
c0560973 1792 this.startWebSocketPing();
5ad8570f 1793 // Start heartbeat
c0560973 1794 this.startHeartbeat();
0a60c33c 1795 // Initialize connectors status
734d790d 1796 for (const connectorId of this.connectors.keys()) {
72092cfc 1797 let connectorStatus: ConnectorStatusEnum | undefined;
734d790d 1798 if (connectorId === 0) {
593cf3f9 1799 continue;
e7aeea18 1800 } else if (
56eb297e
JB
1801 !this.getConnectorStatus(connectorId)?.status &&
1802 (this.isChargingStationAvailable() === false ||
1789ba2c 1803 this.isConnectorAvailable(connectorId) === false)
e7aeea18 1804 ) {
6e939d9e 1805 connectorStatus = ConnectorStatusEnum.UNAVAILABLE;
45c0ae82
JB
1806 } else if (
1807 !this.getConnectorStatus(connectorId)?.status &&
1808 this.getConnectorStatus(connectorId)?.bootStatus
1809 ) {
1810 // Set boot status in template at startup
72092cfc 1811 connectorStatus = this.getConnectorStatus(connectorId)?.bootStatus;
56eb297e
JB
1812 } else if (this.getConnectorStatus(connectorId)?.status) {
1813 // Set previous status at startup
72092cfc 1814 connectorStatus = this.getConnectorStatus(connectorId)?.status;
5ad8570f 1815 } else {
56eb297e 1816 // Set default status
6e939d9e 1817 connectorStatus = ConnectorStatusEnum.AVAILABLE;
5ad8570f 1818 }
56eb297e
JB
1819 await this.ocppRequestService.requestHandler<
1820 StatusNotificationRequest,
1821 StatusNotificationResponse
6e939d9e
JB
1822 >(
1823 this,
1824 RequestCommand.STATUS_NOTIFICATION,
1825 OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
1826 );
1827 this.getConnectorStatus(connectorId).status = connectorStatus;
5ad8570f 1828 }
c9a4f9ea
JB
1829 if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
1830 await this.ocppRequestService.requestHandler<
1831 FirmwareStatusNotificationRequest,
1832 FirmwareStatusNotificationResponse
1833 >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1834 status: FirmwareStatus.Installed,
1835 });
1836 this.stationInfo.firmwareStatus = FirmwareStatus.Installed;
c9a4f9ea 1837 }
3637ca2c 1838
0a60c33c 1839 // Start the ATG
60ddad53 1840 if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
4f69be04 1841 this.startAutomaticTransactionGenerator();
fa7bccf4 1842 }
aa428a31 1843 this.wsConnectionRestarted === true && this.flushMessageBuffer();
fa7bccf4
JB
1844 }
1845
e7aeea18
JB
1846 private async stopMessageSequence(
1847 reason: StopTransactionReason = StopTransactionReason.NONE
1848 ): Promise<void> {
136c90ba 1849 // Stop WebSocket ping
c0560973 1850 this.stopWebSocketPing();
79411696 1851 // Stop heartbeat
c0560973 1852 this.stopHeartbeat();
fa7bccf4 1853 // Stop ongoing transactions
b20eb107 1854 if (this.automaticTransactionGenerator?.started === true) {
60ddad53
JB
1855 this.stopAutomaticTransactionGenerator();
1856 } else {
1857 await this.stopRunningTransactions(reason);
79411696 1858 }
45c0ae82
JB
1859 for (const connectorId of this.connectors.keys()) {
1860 if (connectorId > 0) {
1861 await this.ocppRequestService.requestHandler<
1862 StatusNotificationRequest,
1863 StatusNotificationResponse
6e939d9e
JB
1864 >(
1865 this,
1866 RequestCommand.STATUS_NOTIFICATION,
1867 OCPPServiceUtils.buildStatusNotificationRequest(
1868 this,
1869 connectorId,
1870 ConnectorStatusEnum.UNAVAILABLE
1871 )
1872 );
72092cfc 1873 this.getConnectorStatus(connectorId).status = undefined;
45c0ae82
JB
1874 }
1875 }
79411696
JB
1876 }
1877
c0560973 1878 private startWebSocketPing(): void {
17ac262c
JB
1879 const webSocketPingInterval: number = ChargingStationConfigurationUtils.getConfigurationKey(
1880 this,
e7aeea18
JB
1881 StandardParametersKey.WebSocketPingInterval
1882 )
1883 ? Utils.convertToInt(
17ac262c
JB
1884 ChargingStationConfigurationUtils.getConfigurationKey(
1885 this,
1886 StandardParametersKey.WebSocketPingInterval
72092cfc 1887 )?.value
e7aeea18 1888 )
9cd3dfb0 1889 : 0;
ad2f27c3
JB
1890 if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
1891 this.webSocketPingSetInterval = setInterval(() => {
56eb297e 1892 if (this.isWebSocketConnectionOpened() === true) {
72092cfc 1893 this.wsConnection?.ping();
136c90ba
JB
1894 }
1895 }, webSocketPingInterval * 1000);
e7aeea18 1896 logger.info(
44eb6026
JB
1897 `${this.logPrefix()} WebSocket ping started every ${Utils.formatDurationSeconds(
1898 webSocketPingInterval
1899 )}`
e7aeea18 1900 );
ad2f27c3 1901 } else if (this.webSocketPingSetInterval) {
e7aeea18 1902 logger.info(
44eb6026
JB
1903 `${this.logPrefix()} WebSocket ping already started every ${Utils.formatDurationSeconds(
1904 webSocketPingInterval
1905 )}`
e7aeea18 1906 );
136c90ba 1907 } else {
e7aeea18
JB
1908 logger.error(
1909 `${this.logPrefix()} WebSocket ping interval set to ${
1910 webSocketPingInterval
1911 ? Utils.formatDurationSeconds(webSocketPingInterval)
1912 : webSocketPingInterval
1913 }, not starting the WebSocket ping`
1914 );
136c90ba
JB
1915 }
1916 }
1917
c0560973 1918 private stopWebSocketPing(): void {
ad2f27c3
JB
1919 if (this.webSocketPingSetInterval) {
1920 clearInterval(this.webSocketPingSetInterval);
136c90ba
JB
1921 }
1922 }
1923
1f5df42a 1924 private getConfiguredSupervisionUrl(): URL {
72092cfc 1925 const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
53ac516c 1926 if (Utils.isNotEmptyArray(supervisionUrls)) {
2dcfe98e
JB
1927 switch (Configuration.getSupervisionUrlDistribution()) {
1928 case SupervisionUrlDistribution.ROUND_ROBIN:
c72f6634
JB
1929 // FIXME
1930 this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
2dcfe98e
JB
1931 break;
1932 case SupervisionUrlDistribution.RANDOM:
c72f6634
JB
1933 this.configuredSupervisionUrlIndex = Math.floor(
1934 Utils.secureRandom() * supervisionUrls.length
1935 );
2dcfe98e 1936 break;
c72f6634
JB
1937 case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
1938 this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
2dcfe98e
JB
1939 break;
1940 default:
e7aeea18
JB
1941 logger.error(
1942 `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
c72f6634 1943 SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
e7aeea18
JB
1944 }`
1945 );
c72f6634 1946 this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
2dcfe98e 1947 break;
c0560973 1948 }
c72f6634 1949 return new URL(supervisionUrls[this.configuredSupervisionUrlIndex]);
c0560973 1950 }
57939a9d 1951 return new URL(supervisionUrls as string);
136c90ba
JB
1952 }
1953
c72f6634 1954 private getHeartbeatInterval(): number {
17ac262c
JB
1955 const HeartbeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
1956 this,
1957 StandardParametersKey.HeartbeatInterval
1958 );
c0560973
JB
1959 if (HeartbeatInterval) {
1960 return Utils.convertToInt(HeartbeatInterval.value) * 1000;
1961 }
17ac262c
JB
1962 const HeartBeatInterval = ChargingStationConfigurationUtils.getConfigurationKey(
1963 this,
1964 StandardParametersKey.HeartBeatInterval
1965 );
c0560973
JB
1966 if (HeartBeatInterval) {
1967 return Utils.convertToInt(HeartBeatInterval.value) * 1000;
0a60c33c 1968 }
b7f9e41d 1969 this.stationInfo?.autoRegister === false &&
e7aeea18
JB
1970 logger.warn(
1971 `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
1972 Constants.DEFAULT_HEARTBEAT_INTERVAL
1973 }`
1974 );
47e22477 1975 return Constants.DEFAULT_HEARTBEAT_INTERVAL;
0a60c33c
JB
1976 }
1977
c0560973 1978 private stopHeartbeat(): void {
ad2f27c3
JB
1979 if (this.heartbeatSetInterval) {
1980 clearInterval(this.heartbeatSetInterval);
7dde0b73 1981 }
5ad8570f
JB
1982 }
1983
55516218 1984 private terminateWSConnection(): void {
56eb297e 1985 if (this.isWebSocketConnectionOpened() === true) {
72092cfc 1986 this.wsConnection?.terminate();
55516218
JB
1987 this.wsConnection = null;
1988 }
1989 }
1990
dd119a6b 1991 private stopMeterValues(connectorId: number) {
734d790d 1992 if (this.getConnectorStatus(connectorId)?.transactionSetInterval) {
72092cfc 1993 clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval);
dd119a6b
JB
1994 }
1995 }
1996
c72f6634 1997 private getReconnectExponentialDelay(): boolean {
a14885a3 1998 return this.stationInfo?.reconnectExponentialDelay ?? false;
5ad8570f
JB
1999 }
2000
aa428a31 2001 private async reconnect(): Promise<void> {
7874b0b1
JB
2002 // Stop WebSocket ping
2003 this.stopWebSocketPing();
136c90ba 2004 // Stop heartbeat
c0560973 2005 this.stopHeartbeat();
5ad8570f 2006 // Stop the ATG if needed
6d9876e7 2007 if (this.automaticTransactionGenerator?.configuration?.stopOnConnectionFailure === true) {
fa7bccf4 2008 this.stopAutomaticTransactionGenerator();
ad2f27c3 2009 }
e7aeea18
JB
2010 if (
2011 this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() ||
2012 this.getAutoReconnectMaxRetries() === -1
2013 ) {
ad2f27c3 2014 this.autoReconnectRetryCount++;
e7aeea18
JB
2015 const reconnectDelay = this.getReconnectExponentialDelay()
2016 ? Utils.exponentialDelay(this.autoReconnectRetryCount)
2017 : this.getConnectionTimeout() * 1000;
1e080116
JB
2018 const reconnectDelayWithdraw = 1000;
2019 const reconnectTimeout =
2020 reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
2021 ? reconnectDelay - reconnectDelayWithdraw
2022 : 0;
e7aeea18 2023 logger.error(
d56ea27c 2024 `${this.logPrefix()} WebSocket connection retry in ${Utils.roundTo(
e7aeea18
JB
2025 reconnectDelay,
2026 2
2027 )}ms, timeout ${reconnectTimeout}ms`
2028 );
032d6efc 2029 await Utils.sleep(reconnectDelay);
e7aeea18 2030 logger.error(
44eb6026 2031 `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}`
e7aeea18
JB
2032 );
2033 this.openWSConnection(
ccb1d6e9 2034 { ...(this.stationInfo?.wsOptions ?? {}), handshakeTimeout: reconnectTimeout },
1e080116 2035 { closeOpened: true }
e7aeea18 2036 );
265e4266 2037 this.wsConnectionRestarted = true;
c0560973 2038 } else if (this.getAutoReconnectMaxRetries() !== -1) {
e7aeea18 2039 logger.error(
d56ea27c 2040 `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
e7aeea18 2041 this.autoReconnectRetryCount
d56ea27c 2042 }) or retries disabled (${this.getAutoReconnectMaxRetries()})`
e7aeea18 2043 );
5ad8570f
JB
2044 }
2045 }
2046
551e477c
JB
2047 private getAutomaticTransactionGeneratorConfigurationFromTemplate():
2048 | AutomaticTransactionGeneratorConfiguration
2049 | undefined {
2050 return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
fa7bccf4
JB
2051 }
2052
a2653482
JB
2053 private initializeConnectorStatus(connectorId: number): void {
2054 this.getConnectorStatus(connectorId).idTagLocalAuthorized = false;
2055 this.getConnectorStatus(connectorId).idTagAuthorized = false;
2056 this.getConnectorStatus(connectorId).transactionRemoteStarted = false;
734d790d
JB
2057 this.getConnectorStatus(connectorId).transactionStarted = false;
2058 this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0;
2059 this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
0a60c33c 2060 }
7dde0b73 2061}