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