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