Refine code formatting rules
[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 148 return new URL(
44eb6026
JB
149 `${
150 this.getSupervisionUrlOcppConfiguration()
151 ? ChargingStationConfigurationUtils.getConfigurationKey(
152 this,
153 this.getSupervisionUrlOcppKey()
154 ).value
155 : this.configuredSupervisionUrl.href
156 }/${this.stationInfo.chargingStationId}`
fa7bccf4 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)
8475b222 389 .catch(error => {
6a8329b4
JB
390 logger.error(
391 `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
392 error
393 );
394 });
c0560973 395 }, this.getHeartbeatInterval());
e7aeea18 396 logger.info(
44eb6026
JB
397 `${this.logPrefix()} Heartbeat started every ${Utils.formatDurationMilliSeconds(
398 this.getHeartbeatInterval()
399 )}`
e7aeea18 400 );
c0560973 401 } else if (this.heartbeatSetInterval) {
e7aeea18 402 logger.info(
44eb6026
JB
403 `${this.logPrefix()} Heartbeat already started every ${Utils.formatDurationMilliSeconds(
404 this.getHeartbeatInterval()
405 )}`
e7aeea18 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 477 )
8475b222 478 .catch(error => {
6a8329b4
JB
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:
44eb6026 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 769 if (this.messageBuffer.size > 0) {
8475b222 770 this.messageBuffer.forEach(message => {
1431af78
JB
771 let beginId: string;
772 let commandName: RequestCommand;
8ca6874c 773 const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
1431af78
JB
774 const isRequest = messageType === MessageType.CALL_MESSAGE;
775 if (isRequest) {
776 [, , commandName] = JSON.parse(message) as OutgoingRequest;
777 beginId = PerformanceStatistics.beginMeasure(commandName);
778 }
779 this.wsConnection.send(message);
780 isRequest && PerformanceStatistics.endMeasure(commandName, beginId);
8ca6874c
JB
781 logger.debug(
782 `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
783 messageType
784 )} payload sent: ${message}`
785 );
8e242273 786 this.messageBuffer.delete(message);
77f00f84
JB
787 });
788 }
789 }
790
1f5df42a
JB
791 private getSupervisionUrlOcppConfiguration(): boolean {
792 return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
12fc74d6
JB
793 }
794
e8e865ea
JB
795 private getSupervisionUrlOcppKey(): string {
796 return this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl;
797 }
798
9214b603 799 private getTemplateFromFile(): ChargingStationTemplate | null {
2484ac1e 800 let template: ChargingStationTemplate = null;
5ad8570f 801 try {
57adbebc
JB
802 if (this.sharedLRUCache.hasChargingStationTemplate(this.stationInfo?.templateHash)) {
803 template = this.sharedLRUCache.getChargingStationTemplate(this.stationInfo.templateHash);
7c72977b
JB
804 } else {
805 const measureId = `${FileType.ChargingStationTemplate} read`;
806 const beginId = PerformanceStatistics.beginMeasure(measureId);
807 template = JSON.parse(
808 fs.readFileSync(this.templateFile, 'utf8')
809 ) as ChargingStationTemplate;
810 PerformanceStatistics.endMeasure(measureId, beginId);
811 template.templateHash = crypto
812 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
813 .update(JSON.stringify(template))
814 .digest('hex');
57adbebc 815 this.sharedLRUCache.setChargingStationTemplate(template);
7c72977b 816 }
5ad8570f 817 } catch (error) {
e7aeea18
JB
818 FileUtils.handleFileException(
819 this.logPrefix(),
a95873d8 820 FileType.ChargingStationTemplate,
2484ac1e 821 this.templateFile,
e7aeea18
JB
822 error as NodeJS.ErrnoException
823 );
5ad8570f 824 }
2484ac1e
JB
825 return template;
826 }
827
7a3a2ebb 828 private getStationInfoFromTemplate(): ChargingStationInfo {
fa7bccf4
JB
829 const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile();
830 if (Utils.isNullOrUndefined(stationTemplate)) {
eaad6e5c 831 const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
ccb1d6e9
JB
832 logger.error(`${this.logPrefix()} ${errorMsg}`);
833 throw new BaseError(errorMsg);
94ec7e96 834 }
fa7bccf4 835 if (Utils.isEmptyObject(stationTemplate)) {
ccb1d6e9
JB
836 const errorMsg = `Empty charging station information from template file ${this.templateFile}`;
837 logger.error(`${this.logPrefix()} ${errorMsg}`);
838 throw new BaseError(errorMsg);
94ec7e96 839 }
2dcfe98e 840 // Deprecation template keys section
17ac262c 841 ChargingStationUtils.warnDeprecatedTemplateKey(
fa7bccf4 842 stationTemplate,
e7aeea18 843 'supervisionUrl',
17ac262c 844 this.templateFile,
ccb1d6e9 845 this.logPrefix(),
e7aeea18
JB
846 "Use 'supervisionUrls' instead"
847 );
17ac262c 848 ChargingStationUtils.convertDeprecatedTemplateKey(
fa7bccf4 849 stationTemplate,
17ac262c
JB
850 'supervisionUrl',
851 'supervisionUrls'
852 );
fa7bccf4
JB
853 const stationInfo: ChargingStationInfo =
854 ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
51c83d6f 855 stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
fa7bccf4
JB
856 stationInfo.chargingStationId = ChargingStationUtils.getChargingStationId(
857 this.index,
858 stationTemplate
859 );
cda96260 860 stationInfo.ocppVersion = stationTemplate.ocppVersion ?? OCPPVersion.VERSION_16;
fa7bccf4
JB
861 ChargingStationUtils.createSerialNumber(stationTemplate, stationInfo);
862 if (!Utils.isEmptyArray(stationTemplate.power)) {
863 stationTemplate.power = stationTemplate.power as number[];
864 const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplate.power.length);
cc6e8ab5 865 stationInfo.maximumPower =
fa7bccf4
JB
866 stationTemplate.powerUnit === PowerUnits.KILO_WATT
867 ? stationTemplate.power[powerArrayRandomIndex] * 1000
868 : stationTemplate.power[powerArrayRandomIndex];
5ad8570f 869 } else {
fa7bccf4 870 stationTemplate.power = stationTemplate.power as number;
cc6e8ab5 871 stationInfo.maximumPower =
fa7bccf4
JB
872 stationTemplate.powerUnit === PowerUnits.KILO_WATT
873 ? stationTemplate.power * 1000
874 : stationTemplate.power;
875 }
3637ca2c
JB
876 stationInfo.firmwareVersionPattern =
877 stationTemplate.firmwareVersionPattern ?? Constants.SEMVER_PATTERN;
878 if (
879 stationInfo.firmwareVersion &&
880 new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) === false
881 ) {
882 logger.warn(
883 `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
884 this.templateFile
885 } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
886 );
887 }
15748260
JB
888 stationInfo.firmwareUpgrade = merge(
889 {
890 reset: true,
891 },
892 stationTemplate.firmwareUpgrade ?? {}
893 );
fa7bccf4
JB
894 stationInfo.resetTime = stationTemplate.resetTime
895 ? stationTemplate.resetTime * 1000
e7aeea18 896 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
c72f6634
JB
897 const configuredMaxConnectors =
898 ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate);
fa7bccf4
JB
899 ChargingStationUtils.checkConfiguredMaxConnectors(
900 configuredMaxConnectors,
901 this.templateFile,
fc040c43 902 this.logPrefix()
fa7bccf4
JB
903 );
904 const templateMaxConnectors =
905 ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
906 ChargingStationUtils.checkTemplateMaxConnectors(
907 templateMaxConnectors,
908 this.templateFile,
fc040c43 909 this.logPrefix()
fa7bccf4
JB
910 );
911 if (
912 configuredMaxConnectors >
913 (stationTemplate?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
914 !stationTemplate?.randomConnectors
915 ) {
916 logger.warn(
917 `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
918 this.templateFile
919 }, forcing random connector configurations affectation`
920 );
921 stationInfo.randomConnectors = true;
922 }
923 // Build connectors if needed (FIXME: should be factored out)
924 this.initializeConnectors(stationInfo, configuredMaxConnectors, templateMaxConnectors);
925 stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
926 ChargingStationUtils.createStationInfoHash(stationInfo);
9ac86a7e 927 return stationInfo;
5ad8570f
JB
928 }
929
ccb1d6e9
JB
930 private getStationInfoFromFile(): ChargingStationInfo | null {
931 let stationInfo: ChargingStationInfo = null;
fa7bccf4
JB
932 this.getStationInfoPersistentConfiguration() &&
933 (stationInfo = this.getConfigurationFromFile()?.stationInfo ?? null);
934 stationInfo && ChargingStationUtils.createStationInfoHash(stationInfo);
f765beaa 935 return stationInfo;
2484ac1e
JB
936 }
937
938 private getStationInfo(): ChargingStationInfo {
939 const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
2484ac1e 940 const stationInfoFromFile: ChargingStationInfo = this.getStationInfoFromFile();
aca53a1a 941 // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
f765beaa 942 if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
01efc60a
JB
943 if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
944 return this.stationInfo;
945 }
2484ac1e 946 return stationInfoFromFile;
f765beaa 947 }
fec4d204
JB
948 stationInfoFromFile &&
949 ChargingStationUtils.propagateSerialNumber(
950 this.getTemplateFromFile(),
951 stationInfoFromFile,
952 stationInfoFromTemplate
953 );
01efc60a 954 return stationInfoFromTemplate;
2484ac1e
JB
955 }
956
957 private saveStationInfo(): void {
ccb1d6e9 958 if (this.getStationInfoPersistentConfiguration()) {
7c72977b 959 this.saveConfiguration();
ccb1d6e9 960 }
2484ac1e
JB
961 }
962
e8e865ea 963 private getOcppPersistentConfiguration(): boolean {
ccb1d6e9
JB
964 return this.stationInfo?.ocppPersistentConfiguration ?? true;
965 }
966
967 private getStationInfoPersistentConfiguration(): boolean {
968 return this.stationInfo?.stationInfoPersistentConfiguration ?? true;
e8e865ea
JB
969 }
970
c0560973 971 private handleUnsupportedVersion(version: OCPPVersion) {
fc040c43
JB
972 const errMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
973 logger.error(`${this.logPrefix()} ${errMsg}`);
6c8f5d90 974 throw new BaseError(errMsg);
c0560973
JB
975 }
976
2484ac1e 977 private initialize(): void {
fa7bccf4 978 this.configurationFile = path.join(
ee5f26a2 979 path.dirname(this.templateFile.replace('station-templates', 'configurations')),
44eb6026 980 `${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
0642c3d2 981 );
b44b779a
JB
982 this.stationInfo = this.getStationInfo();
983 this.saveStationInfo();
7a3a2ebb 984 // Avoid duplication of connectors related information in RAM
94ec7e96 985 this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
fa7bccf4 986 this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
ad774cec 987 if (this.getEnableStatistics() === true) {
0642c3d2 988 this.performanceStatistics = PerformanceStatistics.getInstance(
51c83d6f 989 this.stationInfo.hashId,
0642c3d2 990 this.stationInfo.chargingStationId,
fa7bccf4 991 this.configuredSupervisionUrl
0642c3d2
JB
992 );
993 }
fa7bccf4
JB
994 this.bootNotificationRequest = ChargingStationUtils.createBootNotificationRequest(
995 this.stationInfo
996 );
fa7bccf4
JB
997 this.powerDivider = this.getPowerDivider();
998 // OCPP configuration
999 this.ocppConfiguration = this.getOcppConfiguration();
1000 this.initializeOcppConfiguration();
cda96260
JB
1001 const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
1002 switch (ocppVersion) {
c0560973 1003 case OCPPVersion.VERSION_16:
e7aeea18 1004 this.ocppIncomingRequestService =
08f130a0 1005 OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>();
e7aeea18 1006 this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
08f130a0 1007 OCPP16ResponseService.getInstance<OCPP16ResponseService>()
e7aeea18 1008 );
c0560973 1009 break;
edd13439
JB
1010 case OCPPVersion.VERSION_20:
1011 case OCPPVersion.VERSION_201:
1012 this.ocppIncomingRequestService =
1013 OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
1014 this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
1015 OCPP20ResponseService.getInstance<OCPP20ResponseService>()
1016 );
1017 break;
c0560973 1018 default:
cda96260 1019 this.handleUnsupportedVersion(ocppVersion);
c0560973
JB
1020 break;
1021 }
b7f9e41d 1022 if (this.stationInfo?.autoRegister === true) {
47e22477 1023 this.bootNotificationResponse = {
d270cc87 1024 currentTime: new Date(),
47e22477 1025 interval: this.getHeartbeatInterval() / 1000,
d270cc87 1026 status: RegistrationStatusEnumType.ACCEPTED,
47e22477
JB
1027 };
1028 }
3637ca2c
JB
1029 if (
1030 this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
1031 this.stationInfo.firmwareVersion &&
1032 this.stationInfo.firmwareVersionPattern
1033 ) {
15748260 1034 const versionStep = this.stationInfo.firmwareUpgrade?.versionUpgrade?.step ?? 1;
5d280aae 1035 const patternGroup: number =
15748260 1036 this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
5d280aae 1037 this.stationInfo.firmwareVersion.split('.').length;
3637ca2c
JB
1038 const match = this.stationInfo.firmwareVersion
1039 .match(new RegExp(this.stationInfo.firmwareVersionPattern))
5d280aae 1040 .slice(1, patternGroup + 1);
3637ca2c 1041 const patchLevelIndex = match.length - 1;
5d280aae
JB
1042 match[patchLevelIndex] = (
1043 Utils.convertToInt(match[patchLevelIndex]) + versionStep
1044 ).toString();
3637ca2c
JB
1045 this.stationInfo.firmwareVersion = match.join('.');
1046 }
147d0e0f
JB
1047 }
1048
2484ac1e 1049 private initializeOcppConfiguration(): void {
17ac262c
JB
1050 if (
1051 !ChargingStationConfigurationUtils.getConfigurationKey(
1052 this,
1053 StandardParametersKey.HeartbeatInterval
1054 )
1055 ) {
1056 ChargingStationConfigurationUtils.addConfigurationKey(
1057 this,
1058 StandardParametersKey.HeartbeatInterval,
1059 '0'
1060 );
f0f65a62 1061 }
17ac262c
JB
1062 if (
1063 !ChargingStationConfigurationUtils.getConfigurationKey(
1064 this,
1065 StandardParametersKey.HeartBeatInterval
1066 )
1067 ) {
1068 ChargingStationConfigurationUtils.addConfigurationKey(
1069 this,
1070 StandardParametersKey.HeartBeatInterval,
1071 '0',
1072 { visible: false }
1073 );
f0f65a62 1074 }
e7aeea18
JB
1075 if (
1076 this.getSupervisionUrlOcppConfiguration() &&
17ac262c 1077 !ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
e7aeea18 1078 ) {
17ac262c
JB
1079 ChargingStationConfigurationUtils.addConfigurationKey(
1080 this,
a59737e3 1081 this.getSupervisionUrlOcppKey(),
fa7bccf4 1082 this.configuredSupervisionUrl.href,
e7aeea18
JB
1083 { reboot: true }
1084 );
e6895390
JB
1085 } else if (
1086 !this.getSupervisionUrlOcppConfiguration() &&
17ac262c 1087 ChargingStationConfigurationUtils.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
e6895390 1088 ) {
17ac262c
JB
1089 ChargingStationConfigurationUtils.deleteConfigurationKey(
1090 this,
1091 this.getSupervisionUrlOcppKey(),
1092 { save: false }
1093 );
12fc74d6 1094 }
cc6e8ab5
JB
1095 if (
1096 this.stationInfo.amperageLimitationOcppKey &&
17ac262c
JB
1097 !ChargingStationConfigurationUtils.getConfigurationKey(
1098 this,
1099 this.stationInfo.amperageLimitationOcppKey
1100 )
cc6e8ab5 1101 ) {
17ac262c
JB
1102 ChargingStationConfigurationUtils.addConfigurationKey(
1103 this,
cc6e8ab5 1104 this.stationInfo.amperageLimitationOcppKey,
17ac262c
JB
1105 (
1106 this.stationInfo.maximumAmperage *
1107 ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
1108 ).toString()
cc6e8ab5
JB
1109 );
1110 }
17ac262c
JB
1111 if (
1112 !ChargingStationConfigurationUtils.getConfigurationKey(
1113 this,
1114 StandardParametersKey.SupportedFeatureProfiles
1115 )
1116 ) {
1117 ChargingStationConfigurationUtils.addConfigurationKey(
1118 this,
e7aeea18 1119 StandardParametersKey.SupportedFeatureProfiles,
b22787b4 1120 `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
e7aeea18
JB
1121 );
1122 }
17ac262c
JB
1123 ChargingStationConfigurationUtils.addConfigurationKey(
1124 this,
e7aeea18
JB
1125 StandardParametersKey.NumberOfConnectors,
1126 this.getNumberOfConnectors().toString(),
a95873d8
JB
1127 { readonly: true },
1128 { overwrite: true }
e7aeea18 1129 );
17ac262c
JB
1130 if (
1131 !ChargingStationConfigurationUtils.getConfigurationKey(
1132 this,
1133 StandardParametersKey.MeterValuesSampledData
1134 )
1135 ) {
1136 ChargingStationConfigurationUtils.addConfigurationKey(
1137 this,
e7aeea18
JB
1138 StandardParametersKey.MeterValuesSampledData,
1139 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1140 );
7abfea5f 1141 }
17ac262c
JB
1142 if (
1143 !ChargingStationConfigurationUtils.getConfigurationKey(
1144 this,
1145 StandardParametersKey.ConnectorPhaseRotation
1146 )
1147 ) {
7e1dc878 1148 const connectorPhaseRotation = [];
734d790d 1149 for (const connectorId of this.connectors.keys()) {
7e1dc878 1150 // AC/DC
734d790d
JB
1151 if (connectorId === 0 && this.getNumberOfPhases() === 0) {
1152 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
1153 } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
1154 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
e7aeea18 1155 // AC
734d790d
JB
1156 } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
1157 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
1158 } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
1159 connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
7e1dc878
JB
1160 }
1161 }
17ac262c
JB
1162 ChargingStationConfigurationUtils.addConfigurationKey(
1163 this,
e7aeea18
JB
1164 StandardParametersKey.ConnectorPhaseRotation,
1165 connectorPhaseRotation.toString()
1166 );
7e1dc878 1167 }
e7aeea18 1168 if (
17ac262c
JB
1169 !ChargingStationConfigurationUtils.getConfigurationKey(
1170 this,
1171 StandardParametersKey.AuthorizeRemoteTxRequests
e7aeea18
JB
1172 )
1173 ) {
17ac262c
JB
1174 ChargingStationConfigurationUtils.addConfigurationKey(
1175 this,
1176 StandardParametersKey.AuthorizeRemoteTxRequests,
1177 'true'
1178 );
36f6a92e 1179 }
17ac262c
JB
1180 if (
1181 !ChargingStationConfigurationUtils.getConfigurationKey(
1182 this,
1183 StandardParametersKey.LocalAuthListEnabled
1184 ) &&
1185 ChargingStationConfigurationUtils.getConfigurationKey(
1186 this,
1187 StandardParametersKey.SupportedFeatureProfiles
1188 )?.value.includes(SupportedFeatureProfiles.LocalAuthListManagement)
1189 ) {
1190 ChargingStationConfigurationUtils.addConfigurationKey(
1191 this,
1192 StandardParametersKey.LocalAuthListEnabled,
1193 'false'
1194 );
1195 }
1196 if (
1197 !ChargingStationConfigurationUtils.getConfigurationKey(
1198 this,
1199 StandardParametersKey.ConnectionTimeOut
1200 )
1201 ) {
1202 ChargingStationConfigurationUtils.addConfigurationKey(
1203 this,
e7aeea18
JB
1204 StandardParametersKey.ConnectionTimeOut,
1205 Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
1206 );
8bce55bf 1207 }
2484ac1e 1208 this.saveOcppConfiguration();
073bd098
JB
1209 }
1210
3d25cc86
JB
1211 private initializeConnectors(
1212 stationInfo: ChargingStationInfo,
fa7bccf4 1213 configuredMaxConnectors: number,
3d25cc86
JB
1214 templateMaxConnectors: number
1215 ): void {
1216 if (!stationInfo?.Connectors && this.connectors.size === 0) {
fc040c43
JB
1217 const logMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
1218 logger.error(`${this.logPrefix()} ${logMsg}`);
3d25cc86
JB
1219 throw new BaseError(logMsg);
1220 }
1221 if (!stationInfo?.Connectors[0]) {
1222 logger.warn(
1223 `${this.logPrefix()} Charging station information from template ${
1224 this.templateFile
1225 } with no connector Id 0 configuration`
1226 );
1227 }
1228 if (stationInfo?.Connectors) {
1229 const connectorsConfigHash = crypto
1230 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
14ecae6a 1231 .update(`${JSON.stringify(stationInfo?.Connectors)}${configuredMaxConnectors.toString()}`)
3d25cc86
JB
1232 .digest('hex');
1233 const connectorsConfigChanged =
1234 this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash;
1235 if (this.connectors?.size === 0 || connectorsConfigChanged) {
1236 connectorsConfigChanged && this.connectors.clear();
1237 this.connectorsConfigurationHash = connectorsConfigHash;
1238 // Add connector Id 0
1239 let lastConnector = '0';
1240 for (lastConnector in stationInfo?.Connectors) {
56eb297e 1241 const connectorStatus = stationInfo?.Connectors[lastConnector];
3d25cc86
JB
1242 const lastConnectorId = Utils.convertToInt(lastConnector);
1243 if (
1244 lastConnectorId === 0 &&
bb83b5ed 1245 this.getUseConnectorId0(stationInfo) === true &&
56eb297e 1246 connectorStatus
3d25cc86 1247 ) {
56eb297e 1248 this.checkStationInfoConnectorStatus(lastConnectorId, connectorStatus);
3d25cc86
JB
1249 this.connectors.set(
1250 lastConnectorId,
56eb297e 1251 Utils.cloneObject<ConnectorStatus>(connectorStatus)
3d25cc86
JB
1252 );
1253 this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.OPERATIVE;
1254 if (Utils.isUndefined(this.getConnectorStatus(lastConnectorId)?.chargingProfiles)) {
1255 this.getConnectorStatus(lastConnectorId).chargingProfiles = [];
1256 }
1257 }
1258 }
1259 // Generate all connectors
1260 if ((stationInfo?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) > 0) {
fa7bccf4 1261 for (let index = 1; index <= configuredMaxConnectors; index++) {
ccb1d6e9 1262 const randConnectorId = stationInfo?.randomConnectors
3d25cc86
JB
1263 ? Utils.getRandomInteger(Utils.convertToInt(lastConnector), 1)
1264 : index;
56eb297e
JB
1265 const connectorStatus = stationInfo?.Connectors[randConnectorId.toString()];
1266 this.checkStationInfoConnectorStatus(randConnectorId, connectorStatus);
1267 this.connectors.set(index, Utils.cloneObject<ConnectorStatus>(connectorStatus));
3d25cc86
JB
1268 this.getConnectorStatus(index).availability = AvailabilityType.OPERATIVE;
1269 if (Utils.isUndefined(this.getConnectorStatus(index)?.chargingProfiles)) {
1270 this.getConnectorStatus(index).chargingProfiles = [];
1271 }
1272 }
1273 }
1274 }
1275 } else {
1276 logger.warn(
1277 `${this.logPrefix()} Charging station information from template ${
1278 this.templateFile
1279 } with no connectors configuration defined, using already defined connectors`
1280 );
1281 }
1282 // Initialize transaction attributes on connectors
1283 for (const connectorId of this.connectors.keys()) {
f5b51071
JB
1284 if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionStarted === true) {
1285 logger.warn(
1286 `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
1287 this.getConnectorStatus(connectorId).transactionId
1288 }`
1289 );
1290 }
1984f194
JB
1291 if (
1292 connectorId > 0 &&
1293 (this.getConnectorStatus(connectorId).transactionStarted === undefined ||
f5b51071 1294 this.getConnectorStatus(connectorId).transactionStarted === null)
1984f194 1295 ) {
3d25cc86
JB
1296 this.initializeConnectorStatus(connectorId);
1297 }
1298 }
1299 }
1300
56eb297e
JB
1301 private checkStationInfoConnectorStatus(
1302 connectorId: number,
1303 connectorStatus: ConnectorStatus
1304 ): void {
1305 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
1306 logger.warn(
1307 `${this.logPrefix()} Charging station information from template ${
1308 this.templateFile
1309 } with connector ${connectorId} status configuration defined, undefine it`
1310 );
1311 connectorStatus.status = undefined;
1312 }
1313 }
1314
7f7b65ca 1315 private getConfigurationFromFile(): ChargingStationConfiguration | null {
073bd098 1316 let configuration: ChargingStationConfiguration = null;
2484ac1e 1317 if (this.configurationFile && fs.existsSync(this.configurationFile)) {
073bd098 1318 try {
57adbebc
JB
1319 if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
1320 configuration = this.sharedLRUCache.getChargingStationConfiguration(
1321 this.configurationFileHash
1322 );
7c72977b
JB
1323 } else {
1324 const measureId = `${FileType.ChargingStationConfiguration} read`;
1325 const beginId = PerformanceStatistics.beginMeasure(measureId);
1326 configuration = JSON.parse(
1327 fs.readFileSync(this.configurationFile, 'utf8')
1328 ) as ChargingStationConfiguration;
1329 PerformanceStatistics.endMeasure(measureId, beginId);
1330 this.configurationFileHash = configuration.configurationHash;
57adbebc 1331 this.sharedLRUCache.setChargingStationConfiguration(configuration);
7c72977b 1332 }
073bd098
JB
1333 } catch (error) {
1334 FileUtils.handleFileException(
1335 this.logPrefix(),
a95873d8 1336 FileType.ChargingStationConfiguration,
073bd098
JB
1337 this.configurationFile,
1338 error as NodeJS.ErrnoException
1339 );
1340 }
1341 }
1342 return configuration;
1343 }
1344
7c72977b 1345 private saveConfiguration(): void {
2484ac1e
JB
1346 if (this.configurationFile) {
1347 try {
2484ac1e
JB
1348 if (!fs.existsSync(path.dirname(this.configurationFile))) {
1349 fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
073bd098 1350 }
ccb1d6e9
JB
1351 const configurationData: ChargingStationConfiguration =
1352 this.getConfigurationFromFile() ?? {};
7c72977b
JB
1353 this.ocppConfiguration?.configurationKey &&
1354 (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
1355 this.stationInfo && (configurationData.stationInfo = this.stationInfo);
1356 delete configurationData.configurationHash;
1357 const configurationHash = crypto
1358 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
1359 .update(JSON.stringify(configurationData))
1360 .digest('hex');
1361 if (this.configurationFileHash !== configurationHash) {
1362 configurationData.configurationHash = configurationHash;
1363 const measureId = `${FileType.ChargingStationConfiguration} write`;
1364 const beginId = PerformanceStatistics.beginMeasure(measureId);
1365 const fileDescriptor = fs.openSync(this.configurationFile, 'w');
1366 fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
1367 fs.closeSync(fileDescriptor);
1368 PerformanceStatistics.endMeasure(measureId, beginId);
57adbebc 1369 this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
7c72977b 1370 this.configurationFileHash = configurationHash;
57adbebc 1371 this.sharedLRUCache.setChargingStationConfiguration(configurationData);
7c72977b
JB
1372 } else {
1373 logger.debug(
1374 `${this.logPrefix()} Not saving unchanged charging station configuration file ${
1375 this.configurationFile
1376 }`
1377 );
2484ac1e 1378 }
2484ac1e
JB
1379 } catch (error) {
1380 FileUtils.handleFileException(
1381 this.logPrefix(),
1382 FileType.ChargingStationConfiguration,
1383 this.configurationFile,
1384 error as NodeJS.ErrnoException
073bd098
JB
1385 );
1386 }
2484ac1e
JB
1387 } else {
1388 logger.error(
01efc60a 1389 `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
2484ac1e 1390 );
073bd098
JB
1391 }
1392 }
1393
ccb1d6e9
JB
1394 private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration | null {
1395 return this.getTemplateFromFile()?.Configuration ?? null;
2484ac1e
JB
1396 }
1397
1398 private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | null {
1399 let configuration: ChargingStationConfiguration = null;
23290150 1400 if (this.getOcppPersistentConfiguration() === true) {
7a3a2ebb
JB
1401 const configurationFromFile = this.getConfigurationFromFile();
1402 configuration = configurationFromFile?.configurationKey && configurationFromFile;
073bd098 1403 }
2484ac1e 1404 configuration && delete configuration.stationInfo;
073bd098 1405 return configuration;
7dde0b73
JB
1406 }
1407
ccb1d6e9 1408 private getOcppConfiguration(): ChargingStationOcppConfiguration | null {
2484ac1e
JB
1409 let ocppConfiguration: ChargingStationOcppConfiguration = this.getOcppConfigurationFromFile();
1410 if (!ocppConfiguration) {
1411 ocppConfiguration = this.getOcppConfigurationFromTemplate();
1412 }
1413 return ocppConfiguration;
1414 }
1415
c0560973 1416 private async onOpen(): Promise<void> {
56eb297e 1417 if (this.isWebSocketConnectionOpened() === true) {
5144f4d1
JB
1418 logger.info(
1419 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`
1420 );
ed6cfcff 1421 if (this.isRegistered() === false) {
5144f4d1
JB
1422 // Send BootNotification
1423 let registrationRetryCount = 0;
1424 do {
f7f98c68 1425 this.bootNotificationResponse = await this.ocppRequestService.requestHandler<
5144f4d1
JB
1426 BootNotificationRequest,
1427 BootNotificationResponse
8bfbc743
JB
1428 >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
1429 skipBufferingOnError: true,
1430 });
ed6cfcff 1431 if (this.isRegistered() === false) {
5144f4d1
JB
1432 this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
1433 await Utils.sleep(
1434 this.bootNotificationResponse?.interval
1435 ? this.bootNotificationResponse.interval * 1000
1436 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
1437 );
1438 }
1439 } while (
ed6cfcff 1440 this.isRegistered() === false &&
5144f4d1
JB
1441 (registrationRetryCount <= this.getRegistrationMaxRetries() ||
1442 this.getRegistrationMaxRetries() === -1)
1443 );
1444 }
ed6cfcff 1445 if (this.isRegistered() === true) {
23290150 1446 if (this.isInAcceptedState() === true) {
94bb24d5 1447 await this.startMessageSequence();
c0560973 1448 }
5144f4d1
JB
1449 } else {
1450 logger.error(
1451 `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`
1452 );
caad9d6b 1453 }
5144f4d1 1454 this.wsConnectionRestarted = false;
aa428a31 1455 this.autoReconnectRetryCount = 0;
5e3cb728 1456 parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
2e6f5966 1457 } else {
5144f4d1
JB
1458 logger.warn(
1459 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
e7aeea18 1460 );
2e6f5966 1461 }
2e6f5966
JB
1462 }
1463
ef7d8c21 1464 private async onClose(code: number, reason: Buffer): Promise<void> {
d09085e9 1465 switch (code) {
6c65a295
JB
1466 // Normal close
1467 case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
c0560973 1468 case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
e7aeea18 1469 logger.info(
5e3cb728 1470 `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
e7aeea18 1471 code
ef7d8c21 1472 )}' and reason '${reason.toString()}'`
e7aeea18 1473 );
c0560973
JB
1474 this.autoReconnectRetryCount = 0;
1475 break;
6c65a295
JB
1476 // Abnormal close
1477 default:
e7aeea18 1478 logger.error(
5e3cb728 1479 `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
e7aeea18 1480 code
ef7d8c21 1481 )}' and reason '${reason.toString()}'`
e7aeea18 1482 );
56eb297e 1483 this.started === true && (await this.reconnect());
c0560973
JB
1484 break;
1485 }
5e3cb728 1486 parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
2e6f5966
JB
1487 }
1488
ef7d8c21 1489 private async onMessage(data: RawData): Promise<void> {
b3ec7bc1
JB
1490 let messageType: number;
1491 let messageId: string;
1492 let commandName: IncomingRequestCommand;
1493 let commandPayload: JsonType;
1494 let errorType: ErrorType;
1495 let errorMessage: string;
1496 let errorDetails: JsonType;
d900c8d7
JB
1497 let responseCallback: ResponseCallback;
1498 let errorCallback: ErrorCallback;
32b02249 1499 let requestCommandName: RequestCommand | IncomingRequestCommand;
b3ec7bc1 1500 let requestPayload: JsonType;
32b02249 1501 let cachedRequest: CachedRequest;
c0560973
JB
1502 let errMsg: string;
1503 try {
b3ec7bc1 1504 const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
53e5fd67 1505 if (Array.isArray(request) === true) {
9934652c 1506 [messageType, messageId] = request;
b3ec7bc1
JB
1507 // Check the type of message
1508 switch (messageType) {
1509 // Incoming Message
1510 case MessageType.CALL_MESSAGE:
9934652c 1511 [, , commandName, commandPayload] = request as IncomingRequest;
0638ddd2 1512 if (this.getEnableStatistics() === true) {
b3ec7bc1
JB
1513 this.performanceStatistics.addRequestStatistic(commandName, messageType);
1514 }
1515 logger.debug(
1516 `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
1517 request
1518 )}`
1519 );
1520 // Process the message
1521 await this.ocppIncomingRequestService.incomingRequestHandler(
08f130a0 1522 this,
b3ec7bc1
JB
1523 messageId,
1524 commandName,
1525 commandPayload
1526 );
1527 break;
1528 // Outcome Message
1529 case MessageType.CALL_RESULT_MESSAGE:
9934652c 1530 [, , commandPayload] = request as Response;
ba7965c4 1531 if (this.requests.has(messageId) === false) {
a2d1c0f1
JB
1532 // Error
1533 throw new OCPPError(
1534 ErrorType.INTERNAL_ERROR,
1535 `Response for unknown message id ${messageId}`,
1536 null,
1537 commandPayload
1538 );
1539 }
b3ec7bc1
JB
1540 // Respond
1541 cachedRequest = this.requests.get(messageId);
53e5fd67 1542 if (Array.isArray(cachedRequest) === true) {
53e07f94 1543 [responseCallback, errorCallback, requestCommandName, requestPayload] = cachedRequest;
b3ec7bc1
JB
1544 } else {
1545 throw new OCPPError(
1546 ErrorType.PROTOCOL_ERROR,
53e5fd67 1547 `Cached request for message id ${messageId} response is not an array`,
c2bc716f
JB
1548 null,
1549 cachedRequest as unknown as JsonType
b3ec7bc1
JB
1550 );
1551 }
1552 logger.debug(
7ec6c5c9 1553 `${this.logPrefix()} << Command '${
2a232a18 1554 requestCommandName ?? Constants.UNKNOWN_COMMAND
7ec6c5c9 1555 }' received response payload: ${JSON.stringify(request)}`
b3ec7bc1 1556 );
a2d1c0f1
JB
1557 responseCallback(commandPayload, requestPayload);
1558 break;
1559 // Error Message
1560 case MessageType.CALL_ERROR_MESSAGE:
1561 [, , errorType, errorMessage, errorDetails] = request as ErrorResponse;
ba7965c4 1562 if (this.requests.has(messageId) === false) {
b3ec7bc1
JB
1563 // Error
1564 throw new OCPPError(
1565 ErrorType.INTERNAL_ERROR,
a2d1c0f1 1566 `Error response for unknown message id ${messageId}`,
c2bc716f 1567 null,
a2d1c0f1 1568 { errorType, errorMessage, errorDetails }
b3ec7bc1
JB
1569 );
1570 }
b3ec7bc1 1571 cachedRequest = this.requests.get(messageId);
53e5fd67 1572 if (Array.isArray(cachedRequest) === true) {
a2d1c0f1 1573 [, errorCallback, requestCommandName] = cachedRequest;
b3ec7bc1
JB
1574 } else {
1575 throw new OCPPError(
1576 ErrorType.PROTOCOL_ERROR,
53e5fd67 1577 `Cached request for message id ${messageId} error response is not an array`,
c2bc716f
JB
1578 null,
1579 cachedRequest as unknown as JsonType
b3ec7bc1
JB
1580 );
1581 }
1582 logger.debug(
7ec6c5c9 1583 `${this.logPrefix()} << Command '${
2a232a18 1584 requestCommandName ?? Constants.UNKNOWN_COMMAND
2cc5d5ec 1585 }' received error response payload: ${JSON.stringify(request)}`
b3ec7bc1 1586 );
a2d1c0f1 1587 errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
b3ec7bc1
JB
1588 break;
1589 // Error
1590 default:
1591 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
fc040c43
JB
1592 errMsg = `Wrong message type ${messageType}`;
1593 logger.error(`${this.logPrefix()} ${errMsg}`);
b3ec7bc1
JB
1594 throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
1595 }
32de5a57 1596 parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
47e22477 1597 } else {
53e5fd67 1598 throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
ba7965c4 1599 request,
ac54a9bb 1600 });
47e22477 1601 }
c0560973
JB
1602 } catch (error) {
1603 // Log
e7aeea18 1604 logger.error(
91a4f151 1605 `${this.logPrefix()} Incoming OCPP command '${
2a232a18 1606 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
9c5d9fa4
JB
1607 }' message '${data.toString()}'${
1608 messageType !== MessageType.CALL_MESSAGE
1609 ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
1610 : ''
1611 } processing error:`,
e7aeea18
JB
1612 error
1613 );
ba7965c4 1614 if (error instanceof OCPPError === false) {
247659af 1615 logger.warn(
91a4f151 1616 `${this.logPrefix()} Error thrown at incoming OCPP command '${
2a232a18 1617 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
fc040c43 1618 }' message '${data.toString()}' handling is not an OCPPError:`,
247659af
JB
1619 error
1620 );
1621 }
13701f69
JB
1622 switch (messageType) {
1623 case MessageType.CALL_MESSAGE:
1624 // Send error
1625 await this.ocppRequestService.sendError(
1626 this,
1627 messageId,
1628 error as OCPPError,
1629 commandName ?? requestCommandName ?? null
1630 );
1631 break;
1632 case MessageType.CALL_RESULT_MESSAGE:
1633 case MessageType.CALL_ERROR_MESSAGE:
1634 if (errorCallback) {
1635 // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
1636 errorCallback(error as OCPPError, false);
1637 } else {
1638 // Remove the request from the cache in case of error at response handling
1639 this.requests.delete(messageId);
1640 }
de4cb8b6 1641 break;
ba7965c4 1642 }
c0560973 1643 }
2328be1e
JB
1644 }
1645
c0560973 1646 private onPing(): void {
44eb6026 1647 logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`);
c0560973
JB
1648 }
1649
1650 private onPong(): void {
44eb6026 1651 logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`);
c0560973
JB
1652 }
1653
9534e74e 1654 private onError(error: WSError): void {
bcc9c3c0 1655 this.closeWSConnection();
44eb6026 1656 logger.error(`${this.logPrefix()} WebSocket error:`, error);
c0560973
JB
1657 }
1658
18bf8274 1659 private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
95bdbf12 1660 if (this.getMeteringPerTransaction() === true) {
07989fad 1661 return (
18bf8274 1662 (rounded === true
07989fad
JB
1663 ? Math.round(connectorStatus?.transactionEnergyActiveImportRegisterValue)
1664 : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
1665 );
1666 }
1667 return (
18bf8274 1668 (rounded === true
07989fad
JB
1669 ? Math.round(connectorStatus?.energyActiveImportRegisterValue)
1670 : connectorStatus?.energyActiveImportRegisterValue) ?? 0
1671 );
1672 }
1673
bb83b5ed 1674 private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean {
fa7bccf4
JB
1675 const localStationInfo = stationInfo ?? this.stationInfo;
1676 return !Utils.isUndefined(localStationInfo.useConnectorId0)
1677 ? localStationInfo.useConnectorId0
e7aeea18 1678 : true;
8bce55bf
JB
1679 }
1680
60ddad53
JB
1681 private getNumberOfRunningTransactions(): number {
1682 let trxCount = 0;
1683 for (const connectorId of this.connectors.keys()) {
1684 if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
1685 trxCount++;
1686 }
1687 }
1688 return trxCount;
1689 }
1690
1691 private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
1692 for (const connectorId of this.connectors.keys()) {
1693 if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
1694 await this.stopTransactionOnConnector(connectorId, reason);
1695 }
1696 }
1697 }
1698
1f761b9a 1699 // 0 for disabling
c72f6634 1700 private getConnectionTimeout(): number {
17ac262c
JB
1701 if (
1702 ChargingStationConfigurationUtils.getConfigurationKey(
1703 this,
1704 StandardParametersKey.ConnectionTimeOut
1705 )
1706 ) {
e7aeea18 1707 return (
17ac262c
JB
1708 parseInt(
1709 ChargingStationConfigurationUtils.getConfigurationKey(
1710 this,
1711 StandardParametersKey.ConnectionTimeOut
1712 ).value
1713 ) ?? Constants.DEFAULT_CONNECTION_TIMEOUT
e7aeea18 1714 );
291cb255 1715 }
291cb255 1716 return Constants.DEFAULT_CONNECTION_TIMEOUT;
3574dfd3
JB
1717 }
1718
1f761b9a 1719 // -1 for unlimited, 0 for disabling
c72f6634 1720 private getAutoReconnectMaxRetries(): number {
ad2f27c3
JB
1721 if (!Utils.isUndefined(this.stationInfo.autoReconnectMaxRetries)) {
1722 return this.stationInfo.autoReconnectMaxRetries;
3574dfd3
JB
1723 }
1724 if (!Utils.isUndefined(Configuration.getAutoReconnectMaxRetries())) {
1725 return Configuration.getAutoReconnectMaxRetries();
1726 }
1727 return -1;
1728 }
1729
ec977daf 1730 // 0 for disabling
c72f6634 1731 private getRegistrationMaxRetries(): number {
ad2f27c3
JB
1732 if (!Utils.isUndefined(this.stationInfo.registrationMaxRetries)) {
1733 return this.stationInfo.registrationMaxRetries;
32a1eb7a
JB
1734 }
1735 return -1;
1736 }
1737
c0560973
JB
1738 private getPowerDivider(): number {
1739 let powerDivider = this.getNumberOfConnectors();
fa7bccf4 1740 if (this.stationInfo?.powerSharedByConnectors) {
c0560973 1741 powerDivider = this.getNumberOfRunningTransactions();
6ecb15e4
JB
1742 }
1743 return powerDivider;
1744 }
1745
fa7bccf4
JB
1746 private getMaximumAmperage(stationInfo: ChargingStationInfo): number | undefined {
1747 const maximumPower = this.getMaximumPower(stationInfo);
1748 switch (this.getCurrentOutType(stationInfo)) {
cc6e8ab5
JB
1749 case CurrentType.AC:
1750 return ACElectricUtils.amperagePerPhaseFromPower(
fa7bccf4 1751 this.getNumberOfPhases(stationInfo),
ad8537a7 1752 maximumPower / this.getNumberOfConnectors(),
fa7bccf4 1753 this.getVoltageOut(stationInfo)
cc6e8ab5
JB
1754 );
1755 case CurrentType.DC:
fa7bccf4 1756 return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo));
cc6e8ab5
JB
1757 }
1758 }
1759
cc6e8ab5
JB
1760 private getAmperageLimitation(): number | undefined {
1761 if (
1762 this.stationInfo.amperageLimitationOcppKey &&
17ac262c
JB
1763 ChargingStationConfigurationUtils.getConfigurationKey(
1764 this,
1765 this.stationInfo.amperageLimitationOcppKey
1766 )
cc6e8ab5
JB
1767 ) {
1768 return (
1769 Utils.convertToInt(
17ac262c
JB
1770 ChargingStationConfigurationUtils.getConfigurationKey(
1771 this,
1772 this.stationInfo.amperageLimitationOcppKey
1773 ).value
1774 ) / ChargingStationUtils.getAmperageLimitationUnitDivider(this.stationInfo)
cc6e8ab5
JB
1775 );
1776 }
1777 }
1778
c0560973 1779 private async startMessageSequence(): Promise<void> {
b7f9e41d 1780 if (this.stationInfo?.autoRegister === true) {
f7f98c68 1781 await this.ocppRequestService.requestHandler<
ef6fa3fb
JB
1782 BootNotificationRequest,
1783 BootNotificationResponse
8bfbc743
JB
1784 >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
1785 skipBufferingOnError: true,
1786 });
6114e6f1 1787 }
136c90ba 1788 // Start WebSocket ping
c0560973 1789 this.startWebSocketPing();
5ad8570f 1790 // Start heartbeat
c0560973 1791 this.startHeartbeat();
0a60c33c 1792 // Initialize connectors status
734d790d 1793 for (const connectorId of this.connectors.keys()) {
6e939d9e 1794 let connectorStatus: ConnectorStatusEnum;
734d790d 1795 if (connectorId === 0) {
593cf3f9 1796 continue;
e7aeea18 1797 } else if (
56eb297e
JB
1798 !this.getConnectorStatus(connectorId)?.status &&
1799 (this.isChargingStationAvailable() === false ||
1789ba2c 1800 this.isConnectorAvailable(connectorId) === false)
e7aeea18 1801 ) {
6e939d9e 1802 connectorStatus = ConnectorStatusEnum.UNAVAILABLE;
45c0ae82
JB
1803 } else if (
1804 !this.getConnectorStatus(connectorId)?.status &&
1805 this.getConnectorStatus(connectorId)?.bootStatus
1806 ) {
1807 // Set boot status in template at startup
6e939d9e 1808 connectorStatus = this.getConnectorStatus(connectorId).bootStatus;
56eb297e
JB
1809 } else if (this.getConnectorStatus(connectorId)?.status) {
1810 // Set previous status at startup
6e939d9e 1811 connectorStatus = this.getConnectorStatus(connectorId).status;
5ad8570f 1812 } else {
56eb297e 1813 // Set default status
6e939d9e 1814 connectorStatus = ConnectorStatusEnum.AVAILABLE;
5ad8570f 1815 }
56eb297e
JB
1816 await this.ocppRequestService.requestHandler<
1817 StatusNotificationRequest,
1818 StatusNotificationResponse
6e939d9e
JB
1819 >(
1820 this,
1821 RequestCommand.STATUS_NOTIFICATION,
1822 OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
1823 );
1824 this.getConnectorStatus(connectorId).status = connectorStatus;
5ad8570f 1825 }
c9a4f9ea
JB
1826 if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
1827 await this.ocppRequestService.requestHandler<
1828 FirmwareStatusNotificationRequest,
1829 FirmwareStatusNotificationResponse
1830 >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1831 status: FirmwareStatus.Installed,
1832 });
1833 this.stationInfo.firmwareStatus = FirmwareStatus.Installed;
c9a4f9ea 1834 }
3637ca2c 1835
0a60c33c 1836 // Start the ATG
60ddad53 1837 if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
4f69be04 1838 this.startAutomaticTransactionGenerator();
fa7bccf4 1839 }
aa428a31 1840 this.wsConnectionRestarted === true && this.flushMessageBuffer();
fa7bccf4
JB
1841 }
1842
e7aeea18
JB
1843 private async stopMessageSequence(
1844 reason: StopTransactionReason = StopTransactionReason.NONE
1845 ): Promise<void> {
136c90ba 1846 // Stop WebSocket ping
c0560973 1847 this.stopWebSocketPing();
79411696 1848 // Stop heartbeat
c0560973 1849 this.stopHeartbeat();
fa7bccf4 1850 // Stop ongoing transactions
b20eb107 1851 if (this.automaticTransactionGenerator?.started === true) {
60ddad53
JB
1852 this.stopAutomaticTransactionGenerator();
1853 } else {
1854 await this.stopRunningTransactions(reason);
79411696 1855 }
45c0ae82
JB
1856 for (const connectorId of this.connectors.keys()) {
1857 if (connectorId > 0) {
1858 await this.ocppRequestService.requestHandler<
1859 StatusNotificationRequest,
1860 StatusNotificationResponse
6e939d9e
JB
1861 >(
1862 this,
1863 RequestCommand.STATUS_NOTIFICATION,
1864 OCPPServiceUtils.buildStatusNotificationRequest(
1865 this,
1866 connectorId,
1867 ConnectorStatusEnum.UNAVAILABLE
1868 )
1869 );
45c0ae82
JB
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 1893 logger.info(
44eb6026
JB
1894 `${this.logPrefix()} WebSocket ping started every ${Utils.formatDurationSeconds(
1895 webSocketPingInterval
1896 )}`
e7aeea18 1897 );
ad2f27c3 1898 } else if (this.webSocketPingSetInterval) {
e7aeea18 1899 logger.info(
44eb6026
JB
1900 `${this.logPrefix()} WebSocket ping already started every ${Utils.formatDurationSeconds(
1901 webSocketPingInterval
1902 )}`
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(
44eb6026 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}