1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import crypto from
'crypto';
5 import path from
'path';
6 import { URL
} from
'url';
7 import { parentPort
} from
'worker_threads';
9 import WebSocket
, { type RawData
} from
'ws';
11 import BaseError from
'../exception/BaseError';
12 import OCPPError from
'../exception/OCPPError';
13 import PerformanceStatistics from
'../performance/PerformanceStatistics';
14 import type { AutomaticTransactionGeneratorConfiguration
} from
'../types/AutomaticTransactionGenerator';
15 import type { ChargingStationConfiguration
} from
'../types/ChargingStationConfiguration';
16 import type { ChargingStationInfo
} from
'../types/ChargingStationInfo';
17 import type { ChargingStationOcppConfiguration
} from
'../types/ChargingStationOcppConfiguration';
19 type ChargingStationTemplate
,
23 } from
'../types/ChargingStationTemplate';
24 import { SupervisionUrlDistribution
} from
'../types/ConfigurationData';
25 import type { ConnectorStatus
} from
'../types/ConnectorStatus';
26 import { FileType
} from
'../types/FileType';
27 import type { JsonType
} from
'../types/JsonType';
28 import { ChargePointErrorCode
} from
'../types/ocpp/ChargePointErrorCode';
29 import { ChargePointStatus
} from
'../types/ocpp/ChargePointStatus';
30 import { ChargingProfile
, ChargingRateUnitType
} from
'../types/ocpp/ChargingProfile';
32 ConnectorPhaseRotation
,
33 StandardParametersKey
,
34 SupportedFeatureProfiles
,
35 VendorDefaultParametersKey
,
36 } from
'../types/ocpp/Configuration';
37 import { ErrorType
} from
'../types/ocpp/ErrorType';
38 import { MessageType
} from
'../types/ocpp/MessageType';
39 import { MeterValue
, MeterValueMeasurand
} from
'../types/ocpp/MeterValues';
40 import { OCPPVersion
} from
'../types/ocpp/OCPPVersion';
43 type BootNotificationRequest
,
46 type HeartbeatRequest
,
48 IncomingRequestCommand
,
49 type MeterValuesRequest
,
51 type ResponseCallback
,
52 type StatusNotificationRequest
,
53 } from
'../types/ocpp/Requests';
55 type BootNotificationResponse
,
57 type HeartbeatResponse
,
58 type MeterValuesResponse
,
59 RegistrationStatusEnumType
,
61 type StatusNotificationResponse
,
62 } from
'../types/ocpp/Responses';
64 StopTransactionReason
,
65 type StopTransactionRequest
,
66 type StopTransactionResponse
,
67 } from
'../types/ocpp/Transaction';
68 import { WSError
, WebSocketCloseEventStatusCode
} from
'../types/WebSocket';
69 import Configuration from
'../utils/Configuration';
70 import Constants from
'../utils/Constants';
71 import { ACElectricUtils
, DCElectricUtils
} from
'../utils/ElectricUtils';
72 import FileUtils from
'../utils/FileUtils';
73 import logger from
'../utils/Logger';
74 import Utils from
'../utils/Utils';
75 import AuthorizedTagsCache from
'./AuthorizedTagsCache';
76 import AutomaticTransactionGenerator from
'./AutomaticTransactionGenerator';
77 import { ChargingStationConfigurationUtils
} from
'./ChargingStationConfigurationUtils';
78 import { ChargingStationUtils
} from
'./ChargingStationUtils';
79 import ChargingStationWorkerBroadcastChannel from
'./ChargingStationWorkerBroadcastChannel';
80 import { MessageChannelUtils
} from
'./MessageChannelUtils';
81 import OCPP16IncomingRequestService from
'./ocpp/1.6/OCPP16IncomingRequestService';
82 import OCPP16RequestService from
'./ocpp/1.6/OCPP16RequestService';
83 import OCPP16ResponseService from
'./ocpp/1.6/OCPP16ResponseService';
84 import { OCPP16ServiceUtils
} from
'./ocpp/1.6/OCPP16ServiceUtils';
85 import OCPP20IncomingRequestService from
'./ocpp/2.0/OCPP20IncomingRequestService';
86 import OCPP20RequestService from
'./ocpp/2.0/OCPP20RequestService';
87 import OCPP20ResponseService from
'./ocpp/2.0/OCPP20ResponseService';
88 import type OCPPIncomingRequestService from
'./ocpp/OCPPIncomingRequestService';
89 import type OCPPRequestService from
'./ocpp/OCPPRequestService';
90 import SharedLRUCache from
'./SharedLRUCache';
92 export default class ChargingStation
{
93 public readonly index
: number;
94 public readonly templateFile
: string;
95 public stationInfo
!: ChargingStationInfo
;
96 public started
: boolean;
97 public starting
: boolean;
98 public authorizedTagsCache
: AuthorizedTagsCache
;
99 public automaticTransactionGenerator
!: AutomaticTransactionGenerator
;
100 public ocppConfiguration
!: ChargingStationOcppConfiguration
;
101 public wsConnection
!: WebSocket
;
102 public readonly connectors
: Map
<number, ConnectorStatus
>;
103 public readonly requests
: Map
<string, CachedRequest
>;
104 public performanceStatistics
!: PerformanceStatistics
;
105 public heartbeatSetInterval
!: NodeJS
.Timeout
;
106 public ocppRequestService
!: OCPPRequestService
;
107 public bootNotificationRequest
!: BootNotificationRequest
;
108 public bootNotificationResponse
!: BootNotificationResponse
| null;
109 public powerDivider
!: number;
110 private stopping
: boolean;
111 private configurationFile
!: string;
112 private configurationFileHash
!: string;
113 private connectorsConfigurationHash
!: string;
114 private ocppIncomingRequestService
!: OCPPIncomingRequestService
;
115 private readonly messageBuffer
: Set
<string>;
116 private configuredSupervisionUrl
!: URL
;
117 private configuredSupervisionUrlIndex
!: number;
118 private wsConnectionRestarted
: boolean;
119 private autoReconnectRetryCount
: number;
120 private templateFileWatcher
!: fs
.FSWatcher
;
121 private readonly sharedLRUCache
: SharedLRUCache
;
122 private webSocketPingSetInterval
!: NodeJS
.Timeout
;
123 private readonly chargingStationWorkerBroadcastChannel
: ChargingStationWorkerBroadcastChannel
;
125 constructor(index
: number, templateFile
: string) {
126 this.started
= false;
127 this.starting
= false;
128 this.stopping
= false;
129 this.wsConnectionRestarted
= false;
130 this.autoReconnectRetryCount
= 0;
132 this.templateFile
= templateFile
;
133 this.connectors
= new Map
<number, ConnectorStatus
>();
134 this.requests
= new Map
<string, CachedRequest
>();
135 this.messageBuffer
= new Set
<string>();
136 this.sharedLRUCache
= SharedLRUCache
.getInstance();
137 this.authorizedTagsCache
= AuthorizedTagsCache
.getInstance();
138 this.chargingStationWorkerBroadcastChannel
= new ChargingStationWorkerBroadcastChannel(this);
143 private get
wsConnectionUrl(): URL
{
145 (this.getSupervisionUrlOcppConfiguration()
146 ? ChargingStationConfigurationUtils
.getConfigurationKey(
148 this.getSupervisionUrlOcppKey()
150 : this.configuredSupervisionUrl
.href
) +
152 this.stationInfo
.chargingStationId
156 public logPrefix(): string {
157 return Utils
.logPrefix(
159 this?.stationInfo?.chargingStationId ??
160 ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())
165 public hasAuthorizedTags(): boolean {
166 return !Utils
.isEmptyArray(
167 this.authorizedTagsCache
.getAuthorizedTags(
168 ChargingStationUtils
.getAuthorizationFile(this.stationInfo
)
173 public getEnableStatistics(): boolean | undefined {
174 return !Utils
.isUndefined(this.stationInfo
.enableStatistics
)
175 ? this.stationInfo
.enableStatistics
179 public getMustAuthorizeAtRemoteStart(): boolean | undefined {
180 return this.stationInfo
.mustAuthorizeAtRemoteStart
?? true;
183 public getPayloadSchemaValidation(): boolean | undefined {
184 return this.stationInfo
.payloadSchemaValidation
?? true;
187 public getNumberOfPhases(stationInfo
?: ChargingStationInfo
): number | undefined {
188 const localStationInfo
: ChargingStationInfo
= stationInfo
?? this.stationInfo
;
189 switch (this.getCurrentOutType(stationInfo
)) {
191 return !Utils
.isUndefined(localStationInfo
.numberOfPhases
)
192 ? localStationInfo
.numberOfPhases
199 public isWebSocketConnectionOpened(): boolean {
200 return this?.wsConnection
?.readyState
=== WebSocket
.OPEN
;
203 public getRegistrationStatus(): RegistrationStatusEnumType
{
204 return this?.bootNotificationResponse
?.status;
207 public isInUnknownState(): boolean {
208 return Utils
.isNullOrUndefined(this?.bootNotificationResponse
?.status);
211 public isInPendingState(): boolean {
212 return this?.bootNotificationResponse
?.status === RegistrationStatusEnumType
.PENDING
;
215 public isInAcceptedState(): boolean {
216 return this?.bootNotificationResponse
?.status === RegistrationStatusEnumType
.ACCEPTED
;
219 public isInRejectedState(): boolean {
220 return this?.bootNotificationResponse
?.status === RegistrationStatusEnumType
.REJECTED
;
223 public isRegistered(): boolean {
225 this.isInUnknownState() === false &&
226 (this.isInAcceptedState() === true || this.isInPendingState() === true)
230 public isChargingStationAvailable(): boolean {
231 return this.getConnectorStatus(0).availability
=== AvailabilityType
.OPERATIVE
;
234 public isConnectorAvailable(id
: number): boolean {
235 return id
> 0 && this.getConnectorStatus(id
).availability
=== AvailabilityType
.OPERATIVE
;
238 public getNumberOfConnectors(): number {
239 return this.connectors
.get(0) ? this.connectors
.size
- 1 : this.connectors
.size
;
242 public getConnectorStatus(id
: number): ConnectorStatus
| undefined {
243 return this.connectors
.get(id
);
246 public getCurrentOutType(stationInfo
?: ChargingStationInfo
): CurrentType
{
247 return (stationInfo
?? this.stationInfo
).currentOutType
?? CurrentType
.AC
;
250 public getOcppStrictCompliance(): boolean {
251 return this.stationInfo
?.ocppStrictCompliance
?? false;
254 public getVoltageOut(stationInfo
?: ChargingStationInfo
): number | undefined {
255 const defaultVoltageOut
= ChargingStationUtils
.getDefaultVoltageOut(
256 this.getCurrentOutType(stationInfo
),
260 const localStationInfo
: ChargingStationInfo
= stationInfo
?? this.stationInfo
;
261 return !Utils
.isUndefined(localStationInfo
.voltageOut
)
262 ? localStationInfo
.voltageOut
266 public getConnectorMaximumAvailablePower(connectorId
: number): number {
267 let connectorAmperageLimitationPowerLimit
: number;
269 !Utils
.isNullOrUndefined(this.getAmperageLimitation()) &&
270 this.getAmperageLimitation() < this.stationInfo
.maximumAmperage
272 connectorAmperageLimitationPowerLimit
=
273 (this.getCurrentOutType() === CurrentType
.AC
274 ? ACElectricUtils
.powerTotal(
275 this.getNumberOfPhases(),
276 this.getVoltageOut(),
277 this.getAmperageLimitation() * this.getNumberOfConnectors()
279 : DCElectricUtils
.power(this.getVoltageOut(), this.getAmperageLimitation())) /
282 const connectorMaximumPower
= this.getMaximumPower() / this.powerDivider
;
283 const connectorChargingProfilePowerLimit
= this.getChargingProfilePowerLimit(connectorId
);
285 isNaN(connectorMaximumPower
) ? Infinity : connectorMaximumPower
,
286 isNaN(connectorAmperageLimitationPowerLimit
)
288 : connectorAmperageLimitationPowerLimit
,
289 isNaN(connectorChargingProfilePowerLimit
) ? Infinity : connectorChargingProfilePowerLimit
293 public getTransactionIdTag(transactionId
: number): string | undefined {
294 for (const connectorId
of this.connectors
.keys()) {
295 if (connectorId
> 0 && this.getConnectorStatus(connectorId
).transactionId
=== transactionId
) {
296 return this.getConnectorStatus(connectorId
).transactionIdTag
;
301 public getOutOfOrderEndMeterValues(): boolean {
302 return this.stationInfo
?.outOfOrderEndMeterValues
?? false;
305 public getBeginEndMeterValues(): boolean {
306 return this.stationInfo
?.beginEndMeterValues
?? false;
309 public getMeteringPerTransaction(): boolean {
310 return this.stationInfo
?.meteringPerTransaction
?? true;
313 public getTransactionDataMeterValues(): boolean {
314 return this.stationInfo
?.transactionDataMeterValues
?? false;
317 public getMainVoltageMeterValues(): boolean {
318 return this.stationInfo
?.mainVoltageMeterValues
?? true;
321 public getPhaseLineToLineVoltageMeterValues(): boolean {
322 return this.stationInfo
?.phaseLineToLineVoltageMeterValues
?? false;
325 public getCustomValueLimitationMeterValues(): boolean {
326 return this.stationInfo
?.customValueLimitationMeterValues
?? true;
329 public getConnectorIdByTransactionId(transactionId
: number): number | undefined {
330 for (const connectorId
of this.connectors
.keys()) {
333 this.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
340 public getEnergyActiveImportRegisterByTransactionId(
341 transactionId
: number,
344 return this.getEnergyActiveImportRegister(
345 this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId
)),
350 public getEnergyActiveImportRegisterByConnectorId(connectorId
: number): number {
351 return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId
));
354 public getAuthorizeRemoteTxRequests(): boolean {
355 const authorizeRemoteTxRequests
= ChargingStationConfigurationUtils
.getConfigurationKey(
357 StandardParametersKey
.AuthorizeRemoteTxRequests
359 return authorizeRemoteTxRequests
360 ? Utils
.convertToBoolean(authorizeRemoteTxRequests
.value
)
364 public getLocalAuthListEnabled(): boolean {
365 const localAuthListEnabled
= ChargingStationConfigurationUtils
.getConfigurationKey(
367 StandardParametersKey
.LocalAuthListEnabled
369 return localAuthListEnabled
? Utils
.convertToBoolean(localAuthListEnabled
.value
) : false;
372 public startHeartbeat(): void {
374 this.getHeartbeatInterval() &&
375 this.getHeartbeatInterval() > 0 &&
376 !this.heartbeatSetInterval
378 // eslint-disable-next-line @typescript-eslint/no-misused-promises
379 this.heartbeatSetInterval
= setInterval(async (): Promise
<void> => {
380 await this.ocppRequestService
.requestHandler
<HeartbeatRequest
, HeartbeatResponse
>(
382 RequestCommand
.HEARTBEAT
384 }, this.getHeartbeatInterval());
387 ' Heartbeat started every ' +
388 Utils
.formatDurationMilliSeconds(this.getHeartbeatInterval())
390 } else if (this.heartbeatSetInterval
) {
393 ' Heartbeat already started every ' +
394 Utils
.formatDurationMilliSeconds(this.getHeartbeatInterval())
398 `${this.logPrefix()} Heartbeat interval set to ${
399 this.getHeartbeatInterval()
400 ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval())
401 : this.getHeartbeatInterval()
402 }, not starting the heartbeat`
407 public restartHeartbeat(): void {
409 this.stopHeartbeat();
411 this.startHeartbeat();
414 public restartWebSocketPing(): void {
415 // Stop WebSocket ping
416 this.stopWebSocketPing();
417 // Start WebSocket ping
418 this.startWebSocketPing();
421 public startMeterValues(connectorId
: number, interval
: number): void {
422 if (connectorId
=== 0) {
424 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`
428 if (!this.getConnectorStatus(connectorId
)) {
430 `${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`
434 if (this.getConnectorStatus(connectorId
)?.transactionStarted
=== false) {
436 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`
440 this.getConnectorStatus(connectorId
)?.transactionStarted
=== true &&
441 !this.getConnectorStatus(connectorId
)?.transactionId
444 `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`
449 // eslint-disable-next-line @typescript-eslint/no-misused-promises
450 this.getConnectorStatus(connectorId
).transactionSetInterval
= setInterval(
451 // eslint-disable-next-line @typescript-eslint/no-misused-promises
452 async (): Promise
<void> => {
453 // FIXME: Implement OCPP version agnostic helpers
454 const meterValue
: MeterValue
= OCPP16ServiceUtils
.buildMeterValue(
457 this.getConnectorStatus(connectorId
).transactionId
,
460 await this.ocppRequestService
.requestHandler
<MeterValuesRequest
, MeterValuesResponse
>(
462 RequestCommand
.METER_VALUES
,
465 transactionId
: this.getConnectorStatus(connectorId
).transactionId
,
466 meterValue
: [meterValue
],
474 `${this.logPrefix()} Charging station ${
475 StandardParametersKey.MeterValueSampleInterval
476 } configuration set to ${
477 interval ? Utils.formatDurationMilliSeconds(interval) : interval
478 }, not sending MeterValues`
483 public start(): void {
484 if (this.started
=== false) {
485 if (this.starting
=== false) {
486 this.starting
= true;
487 if (this.getEnableStatistics()) {
488 this.performanceStatistics
.start();
490 this.openWSConnection();
491 // Monitor charging station template file
492 this.templateFileWatcher
= FileUtils
.watchJsonFile(
494 FileType
.ChargingStationTemplate
,
497 (event
, filename
): void => {
498 if (filename
&& event
=== 'change') {
501 `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
503 } file have changed, reload`
505 this.sharedLRUCache
.deleteChargingStationTemplate(this.stationInfo
?.templateHash
);
509 this.stopAutomaticTransactionGenerator();
511 this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable
=== true
513 this.startAutomaticTransactionGenerator();
515 if (this.getEnableStatistics()) {
516 this.performanceStatistics
.restart();
518 this.performanceStatistics
.stop();
520 // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
523 `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
531 parentPort
.postMessage(MessageChannelUtils
.buildStartedMessage(this));
532 this.starting
= false;
534 logger
.warn(`${this.logPrefix()} Charging station is already starting...`);
537 logger
.warn(`${this.logPrefix()} Charging station is already started...`);
541 public async stop(reason
?: StopTransactionReason
): Promise
<void> {
542 if (this.started
=== true) {
543 if (this.stopping
=== false) {
544 this.stopping
= true;
545 await this.stopMessageSequence(reason
);
546 this.closeWSConnection();
547 if (this.getEnableStatistics()) {
548 this.performanceStatistics
.stop();
550 this.sharedLRUCache
.deleteChargingStationConfiguration(this.configurationFileHash
);
551 this.templateFileWatcher
.close();
552 this.sharedLRUCache
.deleteChargingStationTemplate(this.stationInfo
?.templateHash
);
553 this.bootNotificationResponse
= null;
554 this.started
= false;
555 parentPort
.postMessage(MessageChannelUtils
.buildStoppedMessage(this));
556 this.stopping
= false;
558 logger
.warn(`${this.logPrefix()} Charging station is already stopping...`);
561 logger
.warn(`${this.logPrefix()} Charging station is already stopped...`);
565 public async reset(reason
?: StopTransactionReason
): Promise
<void> {
566 await this.stop(reason
);
567 await Utils
.sleep(this.stationInfo
.resetTime
);
572 public saveOcppConfiguration(): void {
573 if (this.getOcppPersistentConfiguration()) {
574 this.saveConfiguration();
578 public resetConnectorStatus(connectorId
: number): void {
579 this.getConnectorStatus(connectorId
).idTagLocalAuthorized
= false;
580 this.getConnectorStatus(connectorId
).idTagAuthorized
= false;
581 this.getConnectorStatus(connectorId
).transactionRemoteStarted
= false;
582 this.getConnectorStatus(connectorId
).transactionStarted
= false;
583 delete this.getConnectorStatus(connectorId
).localAuthorizeIdTag
;
584 delete this.getConnectorStatus(connectorId
).authorizeIdTag
;
585 delete this.getConnectorStatus(connectorId
).transactionId
;
586 delete this.getConnectorStatus(connectorId
).transactionIdTag
;
587 this.getConnectorStatus(connectorId
).transactionEnergyActiveImportRegisterValue
= 0;
588 delete this.getConnectorStatus(connectorId
).transactionBeginMeterValue
;
589 this.stopMeterValues(connectorId
);
590 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
593 public hasFeatureProfile(featureProfile
: SupportedFeatureProfiles
): boolean {
594 return ChargingStationConfigurationUtils
.getConfigurationKey(
596 StandardParametersKey
.SupportedFeatureProfiles
597 )?.value
.includes(featureProfile
);
600 public bufferMessage(message
: string): void {
601 this.messageBuffer
.add(message
);
604 public openWSConnection(
605 options
: WsOptions
= this.stationInfo
?.wsOptions
?? {},
606 params
: { closeOpened
?: boolean; terminateOpened
?: boolean } = {
608 terminateOpened
: false,
611 options
.handshakeTimeout
= options
?.handshakeTimeout
?? this.getConnectionTimeout() * 1000;
612 params
.closeOpened
= params
?.closeOpened
?? false;
613 params
.terminateOpened
= params
?.terminateOpened
?? false;
614 if (this.started
=== false && this.starting
=== false) {
616 `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} on stopped charging station`
621 !Utils
.isNullOrUndefined(this.stationInfo
.supervisionUser
) &&
622 !Utils
.isNullOrUndefined(this.stationInfo
.supervisionPassword
)
624 options
.auth
= `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
626 if (params
?.closeOpened
) {
627 this.closeWSConnection();
629 if (params
?.terminateOpened
) {
630 this.terminateWSConnection();
632 const ocppVersion
= this.getOcppVersion();
633 let protocol
: string;
634 switch (ocppVersion
) {
635 case OCPPVersion
.VERSION_16
:
636 case OCPPVersion
.VERSION_20
:
637 case OCPPVersion
.VERSION_201
:
638 protocol
= 'ocpp' + ocppVersion
;
641 this.handleUnsupportedVersion(ocppVersion
);
645 if (this.isWebSocketConnectionOpened() === true) {
647 `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`
653 `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
656 this.wsConnection
= new WebSocket(this.wsConnectionUrl
, protocol
, options
);
658 // Handle WebSocket message
659 this.wsConnection
.on(
661 this.onMessage
.bind(this) as (this: WebSocket
, data
: RawData
, isBinary
: boolean) => void
663 // Handle WebSocket error
664 this.wsConnection
.on(
666 this.onError
.bind(this) as (this: WebSocket
, error
: Error) => void
668 // Handle WebSocket close
669 this.wsConnection
.on(
671 this.onClose
.bind(this) as (this: WebSocket
, code
: number, reason
: Buffer
) => void
673 // Handle WebSocket open
674 this.wsConnection
.on('open', this.onOpen
.bind(this) as (this: WebSocket
) => void);
675 // Handle WebSocket ping
676 this.wsConnection
.on('ping', this.onPing
.bind(this) as (this: WebSocket
, data
: Buffer
) => void);
677 // Handle WebSocket pong
678 this.wsConnection
.on('pong', this.onPong
.bind(this) as (this: WebSocket
, data
: Buffer
) => void);
681 public closeWSConnection(): void {
682 if (this.isWebSocketConnectionOpened() === true) {
683 this.wsConnection
.close();
684 this.wsConnection
= null;
688 public startAutomaticTransactionGenerator(
689 connectorIds
?: number[],
690 automaticTransactionGeneratorConfiguration
?: AutomaticTransactionGeneratorConfiguration
692 this.automaticTransactionGenerator
= AutomaticTransactionGenerator
.getInstance(
693 automaticTransactionGeneratorConfiguration
??
694 this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
697 if (!Utils
.isEmptyArray(connectorIds
)) {
698 for (const connectorId
of connectorIds
) {
699 this.automaticTransactionGenerator
.startConnector(connectorId
);
702 this.automaticTransactionGenerator
.start();
704 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
707 public stopAutomaticTransactionGenerator(connectorIds
?: number[]): void {
708 if (!Utils
.isEmptyArray(connectorIds
)) {
709 for (const connectorId
of connectorIds
) {
710 this.automaticTransactionGenerator
?.stopConnector(connectorId
);
713 this.automaticTransactionGenerator
?.stop();
715 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
718 public async stopTransactionOnConnector(
720 reason
= StopTransactionReason
.NONE
721 ): Promise
<StopTransactionResponse
> {
722 const transactionId
= this.getConnectorStatus(connectorId
).transactionId
;
724 this.getBeginEndMeterValues() === true &&
725 this.getOcppStrictCompliance() === true &&
726 this.getOutOfOrderEndMeterValues() === false
728 // FIXME: Implement OCPP version agnostic helpers
729 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
732 this.getEnergyActiveImportRegisterByTransactionId(transactionId
)
734 await this.ocppRequestService
.requestHandler
<MeterValuesRequest
, MeterValuesResponse
>(
736 RequestCommand
.METER_VALUES
,
740 meterValue
: [transactionEndMeterValue
],
744 return this.ocppRequestService
.requestHandler
<StopTransactionRequest
, StopTransactionResponse
>(
746 RequestCommand
.STOP_TRANSACTION
,
749 meterStop
: this.getEnergyActiveImportRegisterByTransactionId(transactionId
, true),
755 private flushMessageBuffer(): void {
756 if (this.messageBuffer
.size
> 0) {
757 this.messageBuffer
.forEach((message
) => {
758 // TODO: evaluate the need to track performance
759 this.wsConnection
.send(message
);
760 this.messageBuffer
.delete(message
);
765 private getSupervisionUrlOcppConfiguration(): boolean {
766 return this.stationInfo
.supervisionUrlOcppConfiguration
?? false;
769 private getSupervisionUrlOcppKey(): string {
770 return this.stationInfo
.supervisionUrlOcppKey
?? VendorDefaultParametersKey
.ConnectionUrl
;
773 private getTemplateFromFile(): ChargingStationTemplate
| null {
774 let template
: ChargingStationTemplate
= null;
776 if (this.sharedLRUCache
.hasChargingStationTemplate(this.stationInfo
?.templateHash
)) {
777 template
= this.sharedLRUCache
.getChargingStationTemplate(this.stationInfo
.templateHash
);
779 const measureId
= `${FileType.ChargingStationTemplate} read`;
780 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
781 template
= JSON
.parse(
782 fs
.readFileSync(this.templateFile
, 'utf8')
783 ) as ChargingStationTemplate
;
784 PerformanceStatistics
.endMeasure(measureId
, beginId
);
785 template
.templateHash
= crypto
786 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
787 .update(JSON
.stringify(template
))
789 this.sharedLRUCache
.setChargingStationTemplate(template
);
792 FileUtils
.handleFileException(
794 FileType
.ChargingStationTemplate
,
796 error
as NodeJS
.ErrnoException
802 private getStationInfoFromTemplate(): ChargingStationInfo
{
803 const stationTemplate
: ChargingStationTemplate
= this.getTemplateFromFile();
804 if (Utils
.isNullOrUndefined(stationTemplate
)) {
805 const errorMsg
= `Failed to read charging station template file ${this.templateFile}`;
806 logger
.error(`${this.logPrefix()} ${errorMsg}`);
807 throw new BaseError(errorMsg
);
809 if (Utils
.isEmptyObject(stationTemplate
)) {
810 const errorMsg
= `Empty charging station information from template file ${this.templateFile}`;
811 logger
.error(`${this.logPrefix()} ${errorMsg}`);
812 throw new BaseError(errorMsg
);
814 // Deprecation template keys section
815 ChargingStationUtils
.warnDeprecatedTemplateKey(
820 "Use 'supervisionUrls' instead"
822 ChargingStationUtils
.convertDeprecatedTemplateKey(
827 const firmwareVersionRegExp
= stationTemplate
.firmwareVersionPattern
828 ? new RegExp(stationTemplate
.firmwareVersionPattern
)
829 : Constants
.SEMVER_REGEXP
;
831 stationTemplate
.firmwareVersion
&&
832 firmwareVersionRegExp
.test(stationTemplate
.firmwareVersion
) === false
835 `${this.logPrefix()} Firmware version '${
836 stationTemplate.firmwareVersion
837 }' in template file ${
839 } does not match regular expression '${firmwareVersionRegExp.toString()}'`
842 const stationInfo
: ChargingStationInfo
=
843 ChargingStationUtils
.stationTemplateToStationInfo(stationTemplate
);
844 stationInfo
.hashId
= ChargingStationUtils
.getHashId(this.index
, stationTemplate
);
845 stationInfo
.chargingStationId
= ChargingStationUtils
.getChargingStationId(
849 ChargingStationUtils
.createSerialNumber(stationTemplate
, stationInfo
);
850 if (!Utils
.isEmptyArray(stationTemplate
.power
)) {
851 stationTemplate
.power
= stationTemplate
.power
as number[];
852 const powerArrayRandomIndex
= Math.floor(Utils
.secureRandom() * stationTemplate
.power
.length
);
853 stationInfo
.maximumPower
=
854 stationTemplate
.powerUnit
=== PowerUnits
.KILO_WATT
855 ? stationTemplate
.power
[powerArrayRandomIndex
] * 1000
856 : stationTemplate
.power
[powerArrayRandomIndex
];
858 stationTemplate
.power
= stationTemplate
.power
as number;
859 stationInfo
.maximumPower
=
860 stationTemplate
.powerUnit
=== PowerUnits
.KILO_WATT
861 ? stationTemplate
.power
* 1000
862 : stationTemplate
.power
;
864 stationInfo
.resetTime
= stationTemplate
.resetTime
865 ? stationTemplate
.resetTime
* 1000
866 : Constants
.CHARGING_STATION_DEFAULT_RESET_TIME
;
867 const configuredMaxConnectors
=
868 ChargingStationUtils
.getConfiguredNumberOfConnectors(stationTemplate
);
869 ChargingStationUtils
.checkConfiguredMaxConnectors(
870 configuredMaxConnectors
,
874 const templateMaxConnectors
=
875 ChargingStationUtils
.getTemplateMaxNumberOfConnectors(stationTemplate
);
876 ChargingStationUtils
.checkTemplateMaxConnectors(
877 templateMaxConnectors
,
882 configuredMaxConnectors
>
883 (stationTemplate
?.Connectors
[0] ? templateMaxConnectors
- 1 : templateMaxConnectors
) &&
884 !stationTemplate
?.randomConnectors
887 `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
889 }, forcing random connector configurations affectation`
891 stationInfo
.randomConnectors
= true;
893 // Build connectors if needed (FIXME: should be factored out)
894 this.initializeConnectors(stationInfo
, configuredMaxConnectors
, templateMaxConnectors
);
895 stationInfo
.maximumAmperage
= this.getMaximumAmperage(stationInfo
);
896 ChargingStationUtils
.createStationInfoHash(stationInfo
);
900 private getStationInfoFromFile(): ChargingStationInfo
| null {
901 let stationInfo
: ChargingStationInfo
= null;
902 this.getStationInfoPersistentConfiguration() &&
903 (stationInfo
= this.getConfigurationFromFile()?.stationInfo
?? null);
904 stationInfo
&& ChargingStationUtils
.createStationInfoHash(stationInfo
);
908 private getStationInfo(): ChargingStationInfo
{
909 const stationInfoFromTemplate
: ChargingStationInfo
= this.getStationInfoFromTemplate();
910 const stationInfoFromFile
: ChargingStationInfo
= this.getStationInfoFromFile();
911 // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
912 if (stationInfoFromFile
?.templateHash
=== stationInfoFromTemplate
.templateHash
) {
913 if (this.stationInfo
?.infoHash
=== stationInfoFromFile
?.infoHash
) {
914 return this.stationInfo
;
916 return stationInfoFromFile
;
918 stationInfoFromFile
&&
919 ChargingStationUtils
.propagateSerialNumber(
920 this.getTemplateFromFile(),
922 stationInfoFromTemplate
924 return stationInfoFromTemplate
;
927 private saveStationInfo(): void {
928 if (this.getStationInfoPersistentConfiguration()) {
929 this.saveConfiguration();
933 private getOcppVersion(): OCPPVersion
{
934 return this.stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
937 private getOcppPersistentConfiguration(): boolean {
938 return this.stationInfo
?.ocppPersistentConfiguration
?? true;
941 private getStationInfoPersistentConfiguration(): boolean {
942 return this.stationInfo
?.stationInfoPersistentConfiguration
?? true;
945 private handleUnsupportedVersion(version
: OCPPVersion
) {
946 const errMsg
= `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
947 logger
.error(`${this.logPrefix()} ${errMsg}`);
948 throw new BaseError(errMsg
);
951 private initialize(): void {
952 this.configurationFile
= path
.join(
953 path
.dirname(this.templateFile
.replace('station-templates', 'configurations')),
954 ChargingStationUtils
.getHashId(this.index
, this.getTemplateFromFile()) + '.json'
956 this.stationInfo
= this.getStationInfo();
957 this.saveStationInfo();
958 // Avoid duplication of connectors related information in RAM
959 this.stationInfo
?.Connectors
&& delete this.stationInfo
.Connectors
;
960 this.configuredSupervisionUrl
= this.getConfiguredSupervisionUrl();
961 if (this.getEnableStatistics()) {
962 this.performanceStatistics
= PerformanceStatistics
.getInstance(
963 this.stationInfo
.hashId
,
964 this.stationInfo
.chargingStationId
,
965 this.configuredSupervisionUrl
968 this.bootNotificationRequest
= ChargingStationUtils
.createBootNotificationRequest(
971 this.powerDivider
= this.getPowerDivider();
972 // OCPP configuration
973 this.ocppConfiguration
= this.getOcppConfiguration();
974 this.initializeOcppConfiguration();
975 switch (this.getOcppVersion()) {
976 case OCPPVersion
.VERSION_16
:
977 this.ocppIncomingRequestService
=
978 OCPP16IncomingRequestService
.getInstance
<OCPP16IncomingRequestService
>();
979 this.ocppRequestService
= OCPP16RequestService
.getInstance
<OCPP16RequestService
>(
980 OCPP16ResponseService
.getInstance
<OCPP16ResponseService
>()
983 case OCPPVersion
.VERSION_20
:
984 case OCPPVersion
.VERSION_201
:
985 this.ocppIncomingRequestService
=
986 OCPP20IncomingRequestService
.getInstance
<OCPP20IncomingRequestService
>();
987 this.ocppRequestService
= OCPP20RequestService
.getInstance
<OCPP20RequestService
>(
988 OCPP20ResponseService
.getInstance
<OCPP20ResponseService
>()
992 this.handleUnsupportedVersion(this.getOcppVersion());
995 if (this.stationInfo
?.autoRegister
=== true) {
996 this.bootNotificationResponse
= {
997 currentTime
: new Date(),
998 interval
: this.getHeartbeatInterval() / 1000,
999 status: RegistrationStatusEnumType
.ACCEPTED
,
1004 private initializeOcppConfiguration(): void {
1006 !ChargingStationConfigurationUtils
.getConfigurationKey(
1008 StandardParametersKey
.HeartbeatInterval
1011 ChargingStationConfigurationUtils
.addConfigurationKey(
1013 StandardParametersKey
.HeartbeatInterval
,
1018 !ChargingStationConfigurationUtils
.getConfigurationKey(
1020 StandardParametersKey
.HeartBeatInterval
1023 ChargingStationConfigurationUtils
.addConfigurationKey(
1025 StandardParametersKey
.HeartBeatInterval
,
1031 this.getSupervisionUrlOcppConfiguration() &&
1032 !ChargingStationConfigurationUtils
.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
1034 ChargingStationConfigurationUtils
.addConfigurationKey(
1036 this.getSupervisionUrlOcppKey(),
1037 this.configuredSupervisionUrl
.href
,
1041 !this.getSupervisionUrlOcppConfiguration() &&
1042 ChargingStationConfigurationUtils
.getConfigurationKey(this, this.getSupervisionUrlOcppKey())
1044 ChargingStationConfigurationUtils
.deleteConfigurationKey(
1046 this.getSupervisionUrlOcppKey(),
1051 this.stationInfo
.amperageLimitationOcppKey
&&
1052 !ChargingStationConfigurationUtils
.getConfigurationKey(
1054 this.stationInfo
.amperageLimitationOcppKey
1057 ChargingStationConfigurationUtils
.addConfigurationKey(
1059 this.stationInfo
.amperageLimitationOcppKey
,
1061 this.stationInfo
.maximumAmperage
*
1062 ChargingStationUtils
.getAmperageLimitationUnitDivider(this.stationInfo
)
1067 !ChargingStationConfigurationUtils
.getConfigurationKey(
1069 StandardParametersKey
.SupportedFeatureProfiles
1072 ChargingStationConfigurationUtils
.addConfigurationKey(
1074 StandardParametersKey
.SupportedFeatureProfiles
,
1075 `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
1078 ChargingStationConfigurationUtils
.addConfigurationKey(
1080 StandardParametersKey
.NumberOfConnectors
,
1081 this.getNumberOfConnectors().toString(),
1086 !ChargingStationConfigurationUtils
.getConfigurationKey(
1088 StandardParametersKey
.MeterValuesSampledData
1091 ChargingStationConfigurationUtils
.addConfigurationKey(
1093 StandardParametersKey
.MeterValuesSampledData
,
1094 MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1098 !ChargingStationConfigurationUtils
.getConfigurationKey(
1100 StandardParametersKey
.ConnectorPhaseRotation
1103 const connectorPhaseRotation
= [];
1104 for (const connectorId
of this.connectors
.keys()) {
1106 if (connectorId
=== 0 && this.getNumberOfPhases() === 0) {
1107 connectorPhaseRotation
.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
1108 } else if (connectorId
> 0 && this.getNumberOfPhases() === 0) {
1109 connectorPhaseRotation
.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
1111 } else if (connectorId
> 0 && this.getNumberOfPhases() === 1) {
1112 connectorPhaseRotation
.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
1113 } else if (connectorId
> 0 && this.getNumberOfPhases() === 3) {
1114 connectorPhaseRotation
.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
1117 ChargingStationConfigurationUtils
.addConfigurationKey(
1119 StandardParametersKey
.ConnectorPhaseRotation
,
1120 connectorPhaseRotation
.toString()
1124 !ChargingStationConfigurationUtils
.getConfigurationKey(
1126 StandardParametersKey
.AuthorizeRemoteTxRequests
1129 ChargingStationConfigurationUtils
.addConfigurationKey(
1131 StandardParametersKey
.AuthorizeRemoteTxRequests
,
1136 !ChargingStationConfigurationUtils
.getConfigurationKey(
1138 StandardParametersKey
.LocalAuthListEnabled
1140 ChargingStationConfigurationUtils
.getConfigurationKey(
1142 StandardParametersKey
.SupportedFeatureProfiles
1143 )?.value
.includes(SupportedFeatureProfiles
.LocalAuthListManagement
)
1145 ChargingStationConfigurationUtils
.addConfigurationKey(
1147 StandardParametersKey
.LocalAuthListEnabled
,
1152 !ChargingStationConfigurationUtils
.getConfigurationKey(
1154 StandardParametersKey
.ConnectionTimeOut
1157 ChargingStationConfigurationUtils
.addConfigurationKey(
1159 StandardParametersKey
.ConnectionTimeOut
,
1160 Constants
.DEFAULT_CONNECTION_TIMEOUT
.toString()
1163 this.saveOcppConfiguration();
1166 private initializeConnectors(
1167 stationInfo
: ChargingStationInfo
,
1168 configuredMaxConnectors
: number,
1169 templateMaxConnectors
: number
1171 if (!stationInfo
?.Connectors
&& this.connectors
.size
=== 0) {
1172 const logMsg
= `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
1173 logger
.error(`${this.logPrefix()} ${logMsg}`);
1174 throw new BaseError(logMsg
);
1176 if (!stationInfo
?.Connectors
[0]) {
1178 `${this.logPrefix()} Charging station information from template ${
1180 } with no connector Id 0 configuration`
1183 if (stationInfo
?.Connectors
) {
1184 const connectorsConfigHash
= crypto
1185 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
1186 .update(JSON
.stringify(stationInfo
?.Connectors
) + configuredMaxConnectors
.toString())
1188 const connectorsConfigChanged
=
1189 this.connectors
?.size
!== 0 && this.connectorsConfigurationHash
!== connectorsConfigHash
;
1190 if (this.connectors
?.size
=== 0 || connectorsConfigChanged
) {
1191 connectorsConfigChanged
&& this.connectors
.clear();
1192 this.connectorsConfigurationHash
= connectorsConfigHash
;
1193 // Add connector Id 0
1194 let lastConnector
= '0';
1195 for (lastConnector
in stationInfo
?.Connectors
) {
1196 const connectorStatus
= stationInfo
?.Connectors
[lastConnector
];
1197 const lastConnectorId
= Utils
.convertToInt(lastConnector
);
1199 lastConnectorId
=== 0 &&
1200 this.getUseConnectorId0(stationInfo
) === true &&
1203 this.checkStationInfoConnectorStatus(lastConnectorId
, connectorStatus
);
1204 this.connectors
.set(
1206 Utils
.cloneObject
<ConnectorStatus
>(connectorStatus
)
1208 this.getConnectorStatus(lastConnectorId
).availability
= AvailabilityType
.OPERATIVE
;
1209 if (Utils
.isUndefined(this.getConnectorStatus(lastConnectorId
)?.chargingProfiles
)) {
1210 this.getConnectorStatus(lastConnectorId
).chargingProfiles
= [];
1214 // Generate all connectors
1215 if ((stationInfo
?.Connectors
[0] ? templateMaxConnectors
- 1 : templateMaxConnectors
) > 0) {
1216 for (let index
= 1; index
<= configuredMaxConnectors
; index
++) {
1217 const randConnectorId
= stationInfo
?.randomConnectors
1218 ? Utils
.getRandomInteger(Utils
.convertToInt(lastConnector
), 1)
1220 const connectorStatus
= stationInfo
?.Connectors
[randConnectorId
.toString()];
1221 this.checkStationInfoConnectorStatus(randConnectorId
, connectorStatus
);
1222 this.connectors
.set(index
, Utils
.cloneObject
<ConnectorStatus
>(connectorStatus
));
1223 this.getConnectorStatus(index
).availability
= AvailabilityType
.OPERATIVE
;
1224 if (Utils
.isUndefined(this.getConnectorStatus(index
)?.chargingProfiles
)) {
1225 this.getConnectorStatus(index
).chargingProfiles
= [];
1232 `${this.logPrefix()} Charging station information from template ${
1234 } with no connectors configuration defined, using already defined connectors`
1237 // Initialize transaction attributes on connectors
1238 for (const connectorId
of this.connectors
.keys()) {
1239 if (connectorId
> 0 && this.getConnectorStatus(connectorId
).transactionStarted
=== true) {
1241 `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
1242 this.getConnectorStatus(connectorId).transactionId
1248 (this.getConnectorStatus(connectorId
).transactionStarted
=== undefined ||
1249 this.getConnectorStatus(connectorId
).transactionStarted
=== null)
1251 this.initializeConnectorStatus(connectorId
);
1256 private checkStationInfoConnectorStatus(
1257 connectorId
: number,
1258 connectorStatus
: ConnectorStatus
1260 if (!Utils
.isNullOrUndefined(connectorStatus
?.status)) {
1262 `${this.logPrefix()} Charging station information from template ${
1264 } with connector ${connectorId} status configuration defined, undefine it`
1266 connectorStatus
.status = undefined;
1270 private getConfigurationFromFile(): ChargingStationConfiguration
| null {
1271 let configuration
: ChargingStationConfiguration
= null;
1272 if (this.configurationFile
&& fs
.existsSync(this.configurationFile
)) {
1274 if (this.sharedLRUCache
.hasChargingStationConfiguration(this.configurationFileHash
)) {
1275 configuration
= this.sharedLRUCache
.getChargingStationConfiguration(
1276 this.configurationFileHash
1279 const measureId
= `${FileType.ChargingStationConfiguration} read`;
1280 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
1281 configuration
= JSON
.parse(
1282 fs
.readFileSync(this.configurationFile
, 'utf8')
1283 ) as ChargingStationConfiguration
;
1284 PerformanceStatistics
.endMeasure(measureId
, beginId
);
1285 this.configurationFileHash
= configuration
.configurationHash
;
1286 this.sharedLRUCache
.setChargingStationConfiguration(configuration
);
1289 FileUtils
.handleFileException(
1291 FileType
.ChargingStationConfiguration
,
1292 this.configurationFile
,
1293 error
as NodeJS
.ErrnoException
1297 return configuration
;
1300 private saveConfiguration(): void {
1301 if (this.configurationFile
) {
1303 if (!fs
.existsSync(path
.dirname(this.configurationFile
))) {
1304 fs
.mkdirSync(path
.dirname(this.configurationFile
), { recursive
: true });
1306 const configurationData
: ChargingStationConfiguration
=
1307 this.getConfigurationFromFile() ?? {};
1308 this.ocppConfiguration
?.configurationKey
&&
1309 (configurationData
.configurationKey
= this.ocppConfiguration
.configurationKey
);
1310 this.stationInfo
&& (configurationData
.stationInfo
= this.stationInfo
);
1311 delete configurationData
.configurationHash
;
1312 const configurationHash
= crypto
1313 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
1314 .update(JSON
.stringify(configurationData
))
1316 if (this.configurationFileHash
!== configurationHash
) {
1317 configurationData
.configurationHash
= configurationHash
;
1318 const measureId
= `${FileType.ChargingStationConfiguration} write`;
1319 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
1320 const fileDescriptor
= fs
.openSync(this.configurationFile
, 'w');
1321 fs
.writeFileSync(fileDescriptor
, JSON
.stringify(configurationData
, null, 2), 'utf8');
1322 fs
.closeSync(fileDescriptor
);
1323 PerformanceStatistics
.endMeasure(measureId
, beginId
);
1324 this.sharedLRUCache
.deleteChargingStationConfiguration(this.configurationFileHash
);
1325 this.configurationFileHash
= configurationHash
;
1326 this.sharedLRUCache
.setChargingStationConfiguration(configurationData
);
1329 `${this.logPrefix()} Not saving unchanged charging station configuration file ${
1330 this.configurationFile
1335 FileUtils
.handleFileException(
1337 FileType
.ChargingStationConfiguration
,
1338 this.configurationFile
,
1339 error
as NodeJS
.ErrnoException
1344 `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
1349 private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration
| null {
1350 return this.getTemplateFromFile()?.Configuration
?? null;
1353 private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration
| null {
1354 let configuration
: ChargingStationConfiguration
= null;
1355 if (this.getOcppPersistentConfiguration() === true) {
1356 const configurationFromFile
= this.getConfigurationFromFile();
1357 configuration
= configurationFromFile
?.configurationKey
&& configurationFromFile
;
1359 configuration
&& delete configuration
.stationInfo
;
1360 return configuration
;
1363 private getOcppConfiguration(): ChargingStationOcppConfiguration
| null {
1364 let ocppConfiguration
: ChargingStationOcppConfiguration
= this.getOcppConfigurationFromFile();
1365 if (!ocppConfiguration
) {
1366 ocppConfiguration
= this.getOcppConfigurationFromTemplate();
1368 return ocppConfiguration
;
1371 private async onOpen(): Promise
<void> {
1372 if (this.isWebSocketConnectionOpened() === true) {
1374 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`
1376 if (this.isRegistered() === false) {
1377 // Send BootNotification
1378 let registrationRetryCount
= 0;
1380 this.bootNotificationResponse
= await this.ocppRequestService
.requestHandler
<
1381 BootNotificationRequest
,
1382 BootNotificationResponse
1383 >(this, RequestCommand
.BOOT_NOTIFICATION
, this.bootNotificationRequest
, {
1384 skipBufferingOnError
: true,
1386 if (this.isRegistered() === false) {
1387 this.getRegistrationMaxRetries() !== -1 && registrationRetryCount
++;
1389 this.bootNotificationResponse
?.interval
1390 ? this.bootNotificationResponse
.interval
* 1000
1391 : Constants
.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
1395 this.isRegistered() === false &&
1396 (registrationRetryCount
<= this.getRegistrationMaxRetries() ||
1397 this.getRegistrationMaxRetries() === -1)
1400 if (this.isRegistered() === true) {
1401 if (this.isInAcceptedState() === true) {
1402 await this.startMessageSequence();
1406 `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`
1409 this.wsConnectionRestarted
= false;
1410 this.autoReconnectRetryCount
= 0;
1411 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
1414 `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`
1419 private async onClose(code
: number, reason
: Buffer
): Promise
<void> {
1422 case WebSocketCloseEventStatusCode
.CLOSE_NORMAL
:
1423 case WebSocketCloseEventStatusCode
.CLOSE_NO_STATUS
:
1425 `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
1427 )}' and reason '${reason.toString()}'`
1429 this.autoReconnectRetryCount
= 0;
1434 `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
1436 )}' and reason '${reason.toString()}'`
1438 this.started
=== true && (await this.reconnect());
1441 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
1444 private async onMessage(data
: RawData
): Promise
<void> {
1445 let messageType
: number;
1446 let messageId
: string;
1447 let commandName
: IncomingRequestCommand
;
1448 let commandPayload
: JsonType
;
1449 let errorType
: ErrorType
;
1450 let errorMessage
: string;
1451 let errorDetails
: JsonType
;
1452 let responseCallback
: ResponseCallback
;
1453 let errorCallback
: ErrorCallback
;
1454 let requestCommandName
: RequestCommand
| IncomingRequestCommand
;
1455 let requestPayload
: JsonType
;
1456 let cachedRequest
: CachedRequest
;
1459 const request
= JSON
.parse(data
.toString()) as IncomingRequest
| Response
| ErrorResponse
;
1460 if (Array.isArray(request
) === true) {
1461 [messageType
, messageId
] = request
;
1462 // Check the type of message
1463 switch (messageType
) {
1465 case MessageType
.CALL_MESSAGE
:
1466 [, , commandName
, commandPayload
] = request
as IncomingRequest
;
1467 if (this.getEnableStatistics() === true) {
1468 this.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
1471 `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
1475 // Process the message
1476 await this.ocppIncomingRequestService
.incomingRequestHandler(
1484 case MessageType
.CALL_RESULT_MESSAGE
:
1485 [, , commandPayload
] = request
as Response
;
1486 if (this.requests
.has(messageId
) === false) {
1488 throw new OCPPError(
1489 ErrorType
.INTERNAL_ERROR
,
1490 `Response for unknown message id ${messageId}`,
1496 cachedRequest
= this.requests
.get(messageId
);
1497 if (Array.isArray(cachedRequest
) === true) {
1498 [responseCallback
, errorCallback
, requestCommandName
, requestPayload
] = cachedRequest
;
1500 throw new OCPPError(
1501 ErrorType
.PROTOCOL_ERROR
,
1502 `Cached request for message id ${messageId} response is not an array`,
1504 cachedRequest
as unknown
as JsonType
1508 `${this.logPrefix()} << Command '${
1509 requestCommandName ?? Constants.UNKNOWN_COMMAND
1510 }' received response payload: ${JSON.stringify(request)}`
1512 responseCallback(commandPayload
, requestPayload
);
1515 case MessageType
.CALL_ERROR_MESSAGE
:
1516 [, , errorType
, errorMessage
, errorDetails
] = request
as ErrorResponse
;
1517 if (this.requests
.has(messageId
) === false) {
1519 throw new OCPPError(
1520 ErrorType
.INTERNAL_ERROR
,
1521 `Error response for unknown message id ${messageId}`,
1523 { errorType
, errorMessage
, errorDetails
}
1526 cachedRequest
= this.requests
.get(messageId
);
1527 if (Array.isArray(cachedRequest
) === true) {
1528 [, errorCallback
, requestCommandName
] = cachedRequest
;
1530 throw new OCPPError(
1531 ErrorType
.PROTOCOL_ERROR
,
1532 `Cached request for message id ${messageId} error response is not an array`,
1534 cachedRequest
as unknown
as JsonType
1538 `${this.logPrefix()} << Command '${
1539 requestCommandName ?? Constants.UNKNOWN_COMMAND
1540 }' received error payload: ${JSON.stringify(request)}`
1542 errorCallback(new OCPPError(errorType
, errorMessage
, requestCommandName
, errorDetails
));
1546 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1547 errMsg
= `Wrong message type ${messageType}`;
1548 logger
.error(`${this.logPrefix()} ${errMsg}`);
1549 throw new OCPPError(ErrorType
.PROTOCOL_ERROR
, errMsg
);
1551 parentPort
.postMessage(MessageChannelUtils
.buildUpdatedMessage(this));
1553 throw new OCPPError(ErrorType
.PROTOCOL_ERROR
, 'Incoming message is not an array', null, {
1560 `${this.logPrefix()} Incoming OCPP command '${
1561 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
1562 }' message '${data.toString()}'${
1563 messageType !== MessageType.CALL_MESSAGE
1564 ? ` matching cached request
'${JSON.stringify(this.requests.get(messageId))}'`
1566 } processing error:`,
1569 if (error
instanceof OCPPError
=== false) {
1571 `${this.logPrefix()} Error thrown at incoming OCPP command '${
1572 commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
1573 }' message '${data.toString()}' handling is not an OCPPError:`,
1577 switch (messageType
) {
1578 case MessageType
.CALL_MESSAGE
:
1580 await this.ocppRequestService
.sendError(
1584 commandName
?? requestCommandName
?? null
1587 case MessageType
.CALL_RESULT_MESSAGE
:
1588 case MessageType
.CALL_ERROR_MESSAGE
:
1589 if (errorCallback
) {
1590 // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
1591 errorCallback(error
as OCPPError
, false);
1593 // Remove the request from the cache in case of error at response handling
1594 this.requests
.delete(messageId
);
1601 private onPing(): void {
1602 logger
.debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
1605 private onPong(): void {
1606 logger
.debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
1609 private onError(error
: WSError
): void {
1610 this.closeWSConnection();
1611 logger
.error(this.logPrefix() + ' WebSocket error:', error
);
1614 private getEnergyActiveImportRegister(
1615 connectorStatus
: ConnectorStatus
,
1618 if (this.getMeteringPerTransaction() === true) {
1621 ? Math.round(connectorStatus
?.transactionEnergyActiveImportRegisterValue
)
1622 : connectorStatus
?.transactionEnergyActiveImportRegisterValue
) ?? 0
1627 ? Math.round(connectorStatus
?.energyActiveImportRegisterValue
)
1628 : connectorStatus
?.energyActiveImportRegisterValue
) ?? 0
1632 private getUseConnectorId0(stationInfo
?: ChargingStationInfo
): boolean {
1633 const localStationInfo
= stationInfo
?? this.stationInfo
;
1634 return !Utils
.isUndefined(localStationInfo
.useConnectorId0
)
1635 ? localStationInfo
.useConnectorId0
1639 private getNumberOfRunningTransactions(): number {
1641 for (const connectorId
of this.connectors
.keys()) {
1642 if (connectorId
> 0 && this.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
1649 private async stopRunningTransactions(reason
= StopTransactionReason
.NONE
): Promise
<void> {
1650 for (const connectorId
of this.connectors
.keys()) {
1651 if (connectorId
> 0 && this.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
1652 await this.stopTransactionOnConnector(connectorId
, reason
);
1658 private getConnectionTimeout(): number {
1660 ChargingStationConfigurationUtils
.getConfigurationKey(
1662 StandardParametersKey
.ConnectionTimeOut
1667 ChargingStationConfigurationUtils
.getConfigurationKey(
1669 StandardParametersKey
.ConnectionTimeOut
1671 ) ?? Constants
.DEFAULT_CONNECTION_TIMEOUT
1674 return Constants
.DEFAULT_CONNECTION_TIMEOUT
;
1677 // -1 for unlimited, 0 for disabling
1678 private getAutoReconnectMaxRetries(): number {
1679 if (!Utils
.isUndefined(this.stationInfo
.autoReconnectMaxRetries
)) {
1680 return this.stationInfo
.autoReconnectMaxRetries
;
1682 if (!Utils
.isUndefined(Configuration
.getAutoReconnectMaxRetries())) {
1683 return Configuration
.getAutoReconnectMaxRetries();
1689 private getRegistrationMaxRetries(): number {
1690 if (!Utils
.isUndefined(this.stationInfo
.registrationMaxRetries
)) {
1691 return this.stationInfo
.registrationMaxRetries
;
1696 private getPowerDivider(): number {
1697 let powerDivider
= this.getNumberOfConnectors();
1698 if (this.stationInfo
?.powerSharedByConnectors
) {
1699 powerDivider
= this.getNumberOfRunningTransactions();
1701 return powerDivider
;
1704 private getMaximumPower(stationInfo
?: ChargingStationInfo
): number {
1705 const localStationInfo
= stationInfo
?? this.stationInfo
;
1706 return (localStationInfo
['maxPower'] as number) ?? localStationInfo
.maximumPower
;
1709 private getMaximumAmperage(stationInfo
: ChargingStationInfo
): number | undefined {
1710 const maximumPower
= this.getMaximumPower(stationInfo
);
1711 switch (this.getCurrentOutType(stationInfo
)) {
1712 case CurrentType
.AC
:
1713 return ACElectricUtils
.amperagePerPhaseFromPower(
1714 this.getNumberOfPhases(stationInfo
),
1715 maximumPower
/ this.getNumberOfConnectors(),
1716 this.getVoltageOut(stationInfo
)
1718 case CurrentType
.DC
:
1719 return DCElectricUtils
.amperage(maximumPower
, this.getVoltageOut(stationInfo
));
1723 private getAmperageLimitation(): number | undefined {
1725 this.stationInfo
.amperageLimitationOcppKey
&&
1726 ChargingStationConfigurationUtils
.getConfigurationKey(
1728 this.stationInfo
.amperageLimitationOcppKey
1733 ChargingStationConfigurationUtils
.getConfigurationKey(
1735 this.stationInfo
.amperageLimitationOcppKey
1737 ) / ChargingStationUtils
.getAmperageLimitationUnitDivider(this.stationInfo
)
1742 private getChargingProfilePowerLimit(connectorId
: number): number | undefined {
1743 let limit
: number, matchingChargingProfile
: ChargingProfile
;
1744 let chargingProfiles
: ChargingProfile
[] = [];
1745 // Get charging profiles for connector and sort by stack level
1746 chargingProfiles
= this.getConnectorStatus(connectorId
).chargingProfiles
.sort(
1747 (a
, b
) => b
.stackLevel
- a
.stackLevel
1749 // Get profiles on connector 0
1750 if (this.getConnectorStatus(0).chargingProfiles
) {
1751 chargingProfiles
.push(
1752 ...this.getConnectorStatus(0).chargingProfiles
.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
)
1755 if (!Utils
.isEmptyArray(chargingProfiles
)) {
1756 const result
= ChargingStationUtils
.getLimitFromChargingProfiles(
1760 if (!Utils
.isNullOrUndefined(result
)) {
1761 limit
= result
.limit
;
1762 matchingChargingProfile
= result
.matchingChargingProfile
;
1763 switch (this.getCurrentOutType()) {
1764 case CurrentType
.AC
:
1766 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
1767 ChargingRateUnitType
.WATT
1769 : ACElectricUtils
.powerTotal(this.getNumberOfPhases(), this.getVoltageOut(), limit
);
1771 case CurrentType
.DC
:
1773 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
1774 ChargingRateUnitType
.WATT
1776 : DCElectricUtils
.power(this.getVoltageOut(), limit
);
1778 const connectorMaximumPower
= this.getMaximumPower() / this.powerDivider
;
1779 if (limit
> connectorMaximumPower
) {
1781 `${this.logPrefix()} Charging profile id ${
1782 matchingChargingProfile.chargingProfileId
1783 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}, dump charging profiles' stack: %j`,
1784 this.getConnectorStatus(connectorId
).chargingProfiles
1786 limit
= connectorMaximumPower
;
1793 private async startMessageSequence(): Promise
<void> {
1794 if (this.stationInfo
?.autoRegister
=== true) {
1795 await this.ocppRequestService
.requestHandler
<
1796 BootNotificationRequest
,
1797 BootNotificationResponse
1798 >(this, RequestCommand
.BOOT_NOTIFICATION
, this.bootNotificationRequest
, {
1799 skipBufferingOnError
: true,
1802 // Start WebSocket ping
1803 this.startWebSocketPing();
1805 this.startHeartbeat();
1806 // Initialize connectors status
1807 for (const connectorId
of this.connectors
.keys()) {
1808 let chargePointStatus
: ChargePointStatus
;
1809 if (connectorId
=== 0) {
1812 !this.getConnectorStatus(connectorId
)?.status &&
1813 (this.isChargingStationAvailable() === false ||
1814 this.isConnectorAvailable(connectorId
) === false)
1816 chargePointStatus
= ChargePointStatus
.UNAVAILABLE
;
1818 !this.getConnectorStatus(connectorId
)?.status &&
1819 this.getConnectorStatus(connectorId
)?.bootStatus
1821 // Set boot status in template at startup
1822 chargePointStatus
= this.getConnectorStatus(connectorId
).bootStatus
;
1823 } else if (this.getConnectorStatus(connectorId
)?.status) {
1824 // Set previous status at startup
1825 chargePointStatus
= this.getConnectorStatus(connectorId
).status;
1827 // Set default status
1828 chargePointStatus
= ChargePointStatus
.AVAILABLE
;
1830 await this.ocppRequestService
.requestHandler
<
1831 StatusNotificationRequest
,
1832 StatusNotificationResponse
1833 >(this, RequestCommand
.STATUS_NOTIFICATION
, {
1835 status: chargePointStatus
,
1836 errorCode
: ChargePointErrorCode
.NO_ERROR
,
1838 this.getConnectorStatus(connectorId
).status = chargePointStatus
;
1841 if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable
=== true) {
1842 this.startAutomaticTransactionGenerator();
1844 this.wsConnectionRestarted
=== true && this.flushMessageBuffer();
1847 private async stopMessageSequence(
1848 reason
: StopTransactionReason
= StopTransactionReason
.NONE
1850 // Stop WebSocket ping
1851 this.stopWebSocketPing();
1853 this.stopHeartbeat();
1854 // Stop ongoing transactions
1855 if (this.automaticTransactionGenerator
?.started
=== true) {
1856 this.stopAutomaticTransactionGenerator();
1858 await this.stopRunningTransactions(reason
);
1860 for (const connectorId
of this.connectors
.keys()) {
1861 if (connectorId
> 0) {
1862 await this.ocppRequestService
.requestHandler
<
1863 StatusNotificationRequest
,
1864 StatusNotificationResponse
1865 >(this, RequestCommand
.STATUS_NOTIFICATION
, {
1867 status: ChargePointStatus
.UNAVAILABLE
,
1868 errorCode
: ChargePointErrorCode
.NO_ERROR
,
1870 this.getConnectorStatus(connectorId
).status = null;
1875 private startWebSocketPing(): void {
1876 const webSocketPingInterval
: number = ChargingStationConfigurationUtils
.getConfigurationKey(
1878 StandardParametersKey
.WebSocketPingInterval
1880 ? Utils
.convertToInt(
1881 ChargingStationConfigurationUtils
.getConfigurationKey(
1883 StandardParametersKey
.WebSocketPingInterval
1887 if (webSocketPingInterval
> 0 && !this.webSocketPingSetInterval
) {
1888 this.webSocketPingSetInterval
= setInterval(() => {
1889 if (this.isWebSocketConnectionOpened() === true) {
1890 this.wsConnection
.ping();
1892 }, webSocketPingInterval
* 1000);
1895 ' WebSocket ping started every ' +
1896 Utils
.formatDurationSeconds(webSocketPingInterval
)
1898 } else if (this.webSocketPingSetInterval
) {
1901 ' WebSocket ping already started every ' +
1902 Utils
.formatDurationSeconds(webSocketPingInterval
)
1906 `${this.logPrefix()} WebSocket ping interval set to ${
1907 webSocketPingInterval
1908 ? Utils.formatDurationSeconds(webSocketPingInterval)
1909 : webSocketPingInterval
1910 }, not starting the WebSocket ping`
1915 private stopWebSocketPing(): void {
1916 if (this.webSocketPingSetInterval
) {
1917 clearInterval(this.webSocketPingSetInterval
);
1921 private getConfiguredSupervisionUrl(): URL
{
1922 const supervisionUrls
= this.stationInfo
.supervisionUrls
?? Configuration
.getSupervisionUrls();
1923 if (!Utils
.isEmptyArray(supervisionUrls
)) {
1924 switch (Configuration
.getSupervisionUrlDistribution()) {
1925 case SupervisionUrlDistribution
.ROUND_ROBIN
:
1927 this.configuredSupervisionUrlIndex
= (this.index
- 1) % supervisionUrls
.length
;
1929 case SupervisionUrlDistribution
.RANDOM
:
1930 this.configuredSupervisionUrlIndex
= Math.floor(
1931 Utils
.secureRandom() * supervisionUrls
.length
1934 case SupervisionUrlDistribution
.CHARGING_STATION_AFFINITY
:
1935 this.configuredSupervisionUrlIndex
= (this.index
- 1) % supervisionUrls
.length
;
1939 `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
1940 SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
1943 this.configuredSupervisionUrlIndex
= (this.index
- 1) % supervisionUrls
.length
;
1946 return new URL(supervisionUrls
[this.configuredSupervisionUrlIndex
]);
1948 return new URL(supervisionUrls
as string);
1951 private getHeartbeatInterval(): number {
1952 const HeartbeatInterval
= ChargingStationConfigurationUtils
.getConfigurationKey(
1954 StandardParametersKey
.HeartbeatInterval
1956 if (HeartbeatInterval
) {
1957 return Utils
.convertToInt(HeartbeatInterval
.value
) * 1000;
1959 const HeartBeatInterval
= ChargingStationConfigurationUtils
.getConfigurationKey(
1961 StandardParametersKey
.HeartBeatInterval
1963 if (HeartBeatInterval
) {
1964 return Utils
.convertToInt(HeartBeatInterval
.value
) * 1000;
1966 this.stationInfo
?.autoRegister
=== false &&
1968 `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
1969 Constants.DEFAULT_HEARTBEAT_INTERVAL
1972 return Constants
.DEFAULT_HEARTBEAT_INTERVAL
;
1975 private stopHeartbeat(): void {
1976 if (this.heartbeatSetInterval
) {
1977 clearInterval(this.heartbeatSetInterval
);
1981 private terminateWSConnection(): void {
1982 if (this.isWebSocketConnectionOpened() === true) {
1983 this.wsConnection
.terminate();
1984 this.wsConnection
= null;
1988 private stopMeterValues(connectorId
: number) {
1989 if (this.getConnectorStatus(connectorId
)?.transactionSetInterval
) {
1990 clearInterval(this.getConnectorStatus(connectorId
).transactionSetInterval
);
1994 private getReconnectExponentialDelay(): boolean {
1995 return !Utils
.isUndefined(this.stationInfo
.reconnectExponentialDelay
)
1996 ? this.stationInfo
.reconnectExponentialDelay
2000 private async reconnect(): Promise
<void> {
2001 // Stop WebSocket ping
2002 this.stopWebSocketPing();
2004 this.stopHeartbeat();
2005 // Stop the ATG if needed
2006 if (this.automaticTransactionGenerator
?.configuration
?.stopOnConnectionFailure
=== true) {
2007 this.stopAutomaticTransactionGenerator();
2010 this.autoReconnectRetryCount
< this.getAutoReconnectMaxRetries() ||
2011 this.getAutoReconnectMaxRetries() === -1
2013 this.autoReconnectRetryCount
++;
2014 const reconnectDelay
= this.getReconnectExponentialDelay()
2015 ? Utils
.exponentialDelay(this.autoReconnectRetryCount
)
2016 : this.getConnectionTimeout() * 1000;
2017 const reconnectDelayWithdraw
= 1000;
2018 const reconnectTimeout
=
2019 reconnectDelay
&& reconnectDelay
- reconnectDelayWithdraw
> 0
2020 ? reconnectDelay
- reconnectDelayWithdraw
2023 `${this.logPrefix()} WebSocket connection retry in ${Utils.roundTo(
2026 )}ms, timeout ${reconnectTimeout}ms`
2028 await Utils
.sleep(reconnectDelay
);
2030 this.logPrefix() + ' WebSocket connection retry #' + this.autoReconnectRetryCount
.toString()
2032 this.openWSConnection(
2033 { ...(this.stationInfo
?.wsOptions
?? {}), handshakeTimeout
: reconnectTimeout
},
2034 { closeOpened
: true }
2036 this.wsConnectionRestarted
= true;
2037 } else if (this.getAutoReconnectMaxRetries() !== -1) {
2039 `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
2040 this.autoReconnectRetryCount
2041 }) or retries disabled (${this.getAutoReconnectMaxRetries()})`
2046 private getAutomaticTransactionGeneratorConfigurationFromTemplate(): AutomaticTransactionGeneratorConfiguration
| null {
2047 return this.getTemplateFromFile()?.AutomaticTransactionGenerator
?? null;
2050 private initializeConnectorStatus(connectorId
: number): void {
2051 this.getConnectorStatus(connectorId
).idTagLocalAuthorized
= false;
2052 this.getConnectorStatus(connectorId
).idTagAuthorized
= false;
2053 this.getConnectorStatus(connectorId
).transactionRemoteStarted
= false;
2054 this.getConnectorStatus(connectorId
).transactionStarted
= false;
2055 this.getConnectorStatus(connectorId
).energyActiveImportRegisterValue
= 0;
2056 this.getConnectorStatus(connectorId
).transactionEnergyActiveImportRegisterValue
= 0;