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