X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=6be15b83e980656b237a7a1586078134dd17ae54;hb=caad9d6b03dbfc507da6d8e79ccbbaf74593e981;hp=6900cdc3c67e2ec9a1d83da627fa99262d39c525;hpb=734d790d9983a43c20f9ffa728f18825625e8119;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 6900cdc3..6be15b83 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -4,8 +4,7 @@ import { AvailabilityType, BootNotificationRequest, CachedRequest, IncomingReque import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Responses'; import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration'; import ChargingStationTemplate, { CurrentType, PowerUnits, Voltage } from '../types/ChargingStationTemplate'; -import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles } from '../types/ocpp/Configuration'; -import { ConnectorStatus, SampledValueTemplate } from '../types/Connectors'; +import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles, VendorDefaultParametersKey } from '../types/ocpp/Configuration'; import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues'; import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket'; import WebSocket, { ClientOptions, Data, OPEN } from 'ws'; @@ -14,8 +13,10 @@ import AutomaticTransactionGenerator from './AutomaticTransactionGenerator'; import { ChargePointStatus } from '../types/ocpp/ChargePointStatus'; import { ChargingProfile } from '../types/ocpp/ChargingProfile'; import ChargingStationInfo from '../types/ChargingStationInfo'; +import { ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker'; import { ClientRequestArgs } from 'http'; import Configuration from '../utils/Configuration'; +import { ConnectorStatus } from '../types/ConnectorStatus'; import Constants from '../utils/Constants'; import { ErrorType } from '../types/ocpp/ErrorType'; import FileUtils from '../utils/FileUtils'; @@ -23,37 +24,40 @@ import { MessageType } from '../types/ocpp/MessageType'; import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService'; import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService'; import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService'; -import OCPPError from './ocpp/OCPPError'; +import OCPPError from '../exception/OCPPError'; import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService'; import OCPPRequestService from './ocpp/OCPPRequestService'; import { OCPPVersion } from '../types/ocpp/OCPPVersion'; import PerformanceStatistics from '../performance/PerformanceStatistics'; +import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates'; import { StopTransactionReason } from '../types/ocpp/Transaction'; +import { SupervisionUrlDistribution } from '../types/ConfigurationData'; import { URL } from 'url'; import Utils from '../utils/Utils'; import crypto from 'crypto'; import fs from 'fs'; import logger from '../utils/Logger'; +import { parentPort } from 'worker_threads'; import path from 'path'; export default class ChargingStation { - public stationTemplateFile: string; + public readonly stationTemplateFile: string; public authorizedTags: string[]; public stationInfo!: ChargingStationInfo; - public connectors: Map; + public readonly connectors: Map; public configuration!: ChargingStationConfiguration; public wsConnection!: WebSocket; - public requests: Map; + public readonly requests: Map; public performanceStatistics!: PerformanceStatistics; public heartbeatSetInterval!: NodeJS.Timeout; public ocppRequestService!: OCPPRequestService; - private index: number; + private readonly index: number; private bootNotificationRequest!: BootNotificationRequest; private bootNotificationResponse!: BootNotificationResponse | null; private connectorsConfigurationHash!: string; private ocppIncomingRequestService!: OCPPIncomingRequestService; - private messageQueue: string[]; - private wsConnectionUrl!: URL; + private readonly messageBuffer: Set; + private wsConfiguredConnectionUrl!: URL; private wsConnectionRestarted: boolean; private stopped: boolean; private autoReconnectRetryCount: number; @@ -71,11 +75,15 @@ export default class ChargingStation { this.autoReconnectRetryCount = 0; this.requests = new Map(); - this.messageQueue = new Array(); + this.messageBuffer = new Set(); this.authorizedTags = this.getAuthorizedTags(); } + get wsConnectionUrl(): URL { + return this.getSupervisionUrlOcppConfiguration() ? new URL(this.getConfigurationKey(this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl).value + '/' + this.stationInfo.chargingStationId) : this.wsConfiguredConnectionUrl; + } + public logPrefix(): string { return Utils.logPrefix(` ${this.stationInfo.chargingStationId} |`); } @@ -111,11 +119,23 @@ export default class ChargingStation { } public isWebSocketConnectionOpened(): boolean { - return this.wsConnection?.readyState === OPEN; + return this?.wsConnection?.readyState === OPEN; + } + + public isInPendingState(): boolean { + return this?.bootNotificationResponse?.status === RegistrationStatus.PENDING; + } + + public isInAcceptedState(): boolean { + return this?.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED; + } + + public isInRejectedState(): boolean { + return this?.bootNotificationResponse?.status === RegistrationStatus.REJECTED; } public isRegistered(): boolean { - return this.bootNotificationResponse?.status === RegistrationStatus.ACCEPTED; + return this.isInAcceptedState() || this.isInPendingState(); } public isChargingStationAvailable(): boolean { @@ -126,6 +146,10 @@ export default class ChargingStation { return this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE; } + public getNumberOfConnectors(): number { + return this.connectors.get(0) ? this.connectors.size - 1 : this.connectors.size; + } + public getConnectorStatus(id: number): ConnectorStatus { return this.connectors.get(id); } @@ -327,6 +351,7 @@ export default class ChargingStation { this.wsConnection.on('ping', this.onPing.bind(this)); // Handle WebSocket pong this.wsConnection.on('pong', this.onPong.bind(this)); + parentPort.postMessage({ id: ChargingStationWorkerMessageEvents.STARTED, data: { id: this.stationInfo.chargingStationId } }); } public async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise { @@ -345,6 +370,7 @@ export default class ChargingStation { this.performanceStatistics.stop(); } this.bootNotificationResponse = null; + parentPort.postMessage({ id: ChargingStationWorkerMessageEvents.STOPPED, data: { id: this.stationInfo.chargingStationId } }); this.stopped = true; } @@ -357,8 +383,11 @@ export default class ChargingStation { }); } - public addConfigurationKey(key: string | StandardParametersKey, value: string, readonly = false, visible = true, reboot = false): void { + public addConfigurationKey(key: string | StandardParametersKey, value: string, options: { readonly?: boolean, visible?: boolean, reboot?: boolean } = { readonly: false, visible: true, reboot: false }): void { const keyFound = this.getConfigurationKey(key); + const readonly = options.readonly; + const visible = options.visible; + const reboot = options.reboot; if (!keyFound) { this.configuration.configurationKey.push({ key, @@ -396,9 +425,12 @@ export default class ChargingStation { !cpReplaced && this.getConnectorStatus(connectorId).chargingProfiles?.push(cp); } - public resetTransactionOnConnector(connectorId: number): void { - this.getConnectorStatus(connectorId).authorized = false; + public resetConnectorStatus(connectorId: number): void { + this.getConnectorStatus(connectorId).idTagLocalAuthorized = false; + this.getConnectorStatus(connectorId).idTagAuthorized = false; + this.getConnectorStatus(connectorId).transactionRemoteStarted = false; this.getConnectorStatus(connectorId).transactionStarted = false; + delete this.getConnectorStatus(connectorId).localAuthorizeIdTag; delete this.getConnectorStatus(connectorId).authorizeIdTag; delete this.getConnectorStatus(connectorId).transactionId; delete this.getConnectorStatus(connectorId).transactionIdTag; @@ -407,32 +439,24 @@ export default class ChargingStation { this.stopMeterValues(connectorId); } - public addToMessageQueue(message: string): void { - let dups = false; - // Handle dups in message queue - for (const bufferedMessage of this.messageQueue) { - // Message already in the queue - if (message === bufferedMessage) { - dups = true; - break; - } - } - if (!dups) { - // Queue message - this.messageQueue.push(message); - } + public bufferMessage(message: string): void { + this.messageBuffer.add(message); } - private flushMessageQueue() { - if (!Utils.isEmptyArray(this.messageQueue)) { - this.messageQueue.forEach((message, index) => { - this.messageQueue.splice(index, 1); + private flushMessageBuffer() { + if (this.messageBuffer.size > 0) { + this.messageBuffer.forEach((message) => { // TODO: evaluate the need to track performance this.wsConnection.send(message); + this.messageBuffer.delete(message); }); } } + private getSupervisionUrlOcppConfiguration(): boolean { + return this.stationInfo.supervisionUrlOcppConfiguration ?? false; + } + private getChargingStationId(stationTemplate: ChargingStationTemplate): string { // In case of multiple instances: add instance index to charging station id const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; @@ -448,9 +472,14 @@ export default class ChargingStation { stationTemplateFromFile = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8')) as ChargingStationTemplate; fs.closeSync(fileDescriptor); } catch (error) { - FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error); + FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error as NodeJS.ErrnoException); } + const chargingStationId = this.getChargingStationId(stationTemplateFromFile); + // Deprecation template keys section + this.warnDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', chargingStationId, 'Use \'supervisionUrls\' instead'); + this.convertDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', 'supervisionUrls'); const stationInfo: ChargingStationInfo = stationTemplateFromFile ?? {} as ChargingStationInfo; + stationInfo.wsOptions = stationTemplateFromFile?.wsOptions ?? {}; if (!Utils.isEmptyArray(stationTemplateFromFile.power)) { stationTemplateFromFile.power = stationTemplateFromFile.power as number[]; const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplateFromFile.power.length); @@ -465,12 +494,12 @@ export default class ChargingStation { } delete stationInfo.power; delete stationInfo.powerUnit; - stationInfo.chargingStationId = this.getChargingStationId(stationTemplateFromFile); + stationInfo.chargingStationId = chargingStationId; stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME; return stationInfo; } - private getOCPPVersion(): OCPPVersion { + private getOcppVersion(): OCPPVersion { return this.stationInfo.ocppVersion ? this.stationInfo.ocppVersion : OCPPVersion.VERSION_16; } @@ -482,14 +511,14 @@ export default class ChargingStation { private initialize(): void { this.stationInfo = this.buildStationInfo(); + this.configuration = this.getTemplateChargingStationConfiguration(); + delete this.stationInfo.Configuration; this.bootNotificationRequest = { chargePointModel: this.stationInfo.chargePointModel, chargePointVendor: this.stationInfo.chargePointVendor, ...!Utils.isUndefined(this.stationInfo.chargeBoxSerialNumberPrefix) && { chargeBoxSerialNumber: this.stationInfo.chargeBoxSerialNumberPrefix }, ...!Utils.isUndefined(this.stationInfo.firmwareVersion) && { firmwareVersion: this.stationInfo.firmwareVersion }, }; - this.configuration = this.getTemplateChargingStationConfiguration(); - this.wsConnectionUrl = new URL(this.getSupervisionURL().href + '/' + this.stationInfo.chargingStationId); // Build connectors if needed const maxConnectors = this.getMaxNumberOfConnectors(); if (maxConnectors <= 0) { @@ -508,8 +537,8 @@ export default class ChargingStation { this.stationInfo.randomConnectors = true; } const connectorsConfigHash = crypto.createHash('sha256').update(JSON.stringify(this.stationInfo.Connectors) + maxConnectors.toString()).digest('hex'); - const connectorsConfigChanged = this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash; - if (!this.connectors || this.connectors.size === 0 || connectorsConfigChanged) { + const connectorsConfigChanged = this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash; + if (this.connectors?.size === 0 || connectorsConfigChanged) { connectorsConfigChanged && (this.connectors.clear()); this.connectorsConfigurationHash = connectorsConfigHash; // Add connector Id 0 @@ -541,20 +570,21 @@ export default class ChargingStation { // Initialize transaction attributes on connectors for (const connectorId of this.connectors.keys()) { if (connectorId > 0 && !this.getConnectorStatus(connectorId)?.transactionStarted) { - this.initTransactionAttributesOnConnector(connectorId); + this.initializeConnectorStatus(connectorId); } } - switch (this.getOCPPVersion()) { + this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionUrl().href + '/' + this.stationInfo.chargingStationId); + switch (this.getOcppVersion()) { case OCPPVersion.VERSION_16: this.ocppIncomingRequestService = new OCPP16IncomingRequestService(this); this.ocppRequestService = new OCPP16RequestService(this, new OCPP16ResponseService(this)); break; default: - this.handleUnsupportedVersion(this.getOCPPVersion()); + this.handleUnsupportedVersion(this.getOcppVersion()); break; } // OCPP parameters - this.initOCPPParameters(); + this.initOcppParameters(); if (this.stationInfo.autoRegister) { this.bootNotificationResponse = { currentTime: new Date().toISOString(), @@ -568,11 +598,14 @@ export default class ChargingStation { } } - private initOCPPParameters(): void { + private initOcppParameters(): void { + if (this.getSupervisionUrlOcppConfiguration() && !this.getConfigurationKey(this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl)) { + this.addConfigurationKey(VendorDefaultParametersKey.ConnectionUrl, this.getConfiguredSupervisionUrl().href, { reboot: true }); + } if (!this.getConfigurationKey(StandardParametersKey.SupportedFeatureProfiles)) { this.addConfigurationKey(StandardParametersKey.SupportedFeatureProfiles, `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.Local_Auth_List_Management},${SupportedFeatureProfiles.Smart_Charging}`); } - this.addConfigurationKey(StandardParametersKey.NumberOfConnectors, this.getNumberOfConnectors().toString(), true); + this.addConfigurationKey(StandardParametersKey.NumberOfConnectors, this.getNumberOfConnectors().toString(), { readonly: true }); if (!this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData)) { this.addConfigurationKey(StandardParametersKey.MeterValuesSampledData, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER); } @@ -619,11 +652,25 @@ export default class ChargingStation { } } while (!this.isRegistered() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1)); } - if (this.isRegistered()) { + if (this.isRegistered() && this.stationInfo.autoRegister) { + await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel, + this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion); + } + if (this.isInAcceptedState()) { await this.startMessageSequence(); this.stopped && (this.stopped = false); if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) { - this.flushMessageQueue(); + this.flushMessageBuffer(); + } + } else if (this.isInPendingState()) { + // The central server shall issue a TriggerMessage to the charging station for the boot notification at the end of its configuration process + while (!this.isInAcceptedState()) { + await Utils.sleep(Constants.CHARGING_STATION_DEFAULT_START_SEQUENCE_DELAY); + } + await this.startMessageSequence(); + this.stopped && (this.stopped = false); + if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) { + this.flushMessageBuffer(); } } else { logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`); @@ -651,7 +698,7 @@ export default class ChargingStation { private async onMessage(data: Data): Promise { let [messageType, messageId, commandName, commandPayload, errorDetails]: IncomingRequest = [0, '', '' as IncomingRequestCommand, {}, {}]; let responseCallback: (payload: Record | string, requestPayload: Record) => void; - let rejectCallback: (error: OCPPError) => void; + let rejectCallback: (error: OCPPError, requestStatistic?: boolean) => void; let requestCommandName: RequestCommand | IncomingRequestCommand; let requestPayload: Record; let cachedRequest: CachedRequest; @@ -711,9 +758,9 @@ export default class ChargingStation { } } catch (error) { // Log - logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data, this.requests.get(messageId), error); + logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error); // Send error - messageType === MessageType.CALL_MESSAGE && await this.ocppRequestService.sendError(messageId, error, commandName); + messageType === MessageType.CALL_MESSAGE && await this.ocppRequestService.sendError(messageId, error as OCPPError, commandName); } } @@ -752,7 +799,7 @@ export default class ChargingStation { authorizedTags = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8')) as string[]; fs.closeSync(fileDescriptor); } catch (error) { - FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error); + FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException); } } else { logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile); @@ -814,7 +861,7 @@ export default class ChargingStation { } private getMaxNumberOfConnectors(): number { - let maxConnectors = 0; + let maxConnectors: number; if (!Utils.isEmptyArray(this.stationInfo.numberOfConnectors)) { const numberOfConnectors = this.stationInfo.numberOfConnectors as number[]; // Distribute evenly the number of connectors @@ -827,10 +874,6 @@ export default class ChargingStation { return maxConnectors; } - private getNumberOfConnectors(): number { - return this.connectors.get(0) ? this.connectors.size - 1 : this.connectors.size; - } - private async startMessageSequence(): Promise { // Start WebSocket ping this.startWebSocketPing(); @@ -917,17 +960,45 @@ export default class ChargingStation { } } - private getSupervisionURL(): URL { - const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionURL ? this.stationInfo.supervisionURL : Configuration.getSupervisionURLs()); - let indexUrl = 0; + private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void { + if (!Utils.isUndefined(template[key])) { + logger.warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`); + } + } + + private convertDeprecatedTemplateKey(template: ChargingStationTemplate, deprecatedKey: string, key: string): void { + if (!Utils.isUndefined(template[deprecatedKey])) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + template[key] = template[deprecatedKey]; + delete template[deprecatedKey]; + } + } + + private getConfiguredSupervisionUrl(): URL { + const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls()); if (!Utils.isEmptyArray(supervisionUrls)) { - if (Configuration.getDistributeStationsToTenantsEqually()) { - indexUrl = this.index % supervisionUrls.length; - } else { - // Get a random url - indexUrl = Math.floor(Utils.secureRandom() * supervisionUrls.length); + let urlIndex = 0; + switch (Configuration.getSupervisionUrlDistribution()) { + case SupervisionUrlDistribution.ROUND_ROBIN: + urlIndex = (this.index - 1) % supervisionUrls.length; + break; + case SupervisionUrlDistribution.RANDOM: + // Get a random url + urlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length); + break; + case SupervisionUrlDistribution.SEQUENTIAL: + if (this.index <= supervisionUrls.length) { + urlIndex = this.index - 1; + } else { + logger.warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`); + } + break; + default: + logger.error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`); + urlIndex = (this.index - 1) % supervisionUrls.length; + break; } - return new URL(supervisionUrls[indexUrl]); + return new URL(supervisionUrls[urlIndex]); } return new URL(supervisionUrls as string); } @@ -951,8 +1022,7 @@ export default class ChargingStation { } } - private openWSConnection(options?: ClientOptions & ClientRequestArgs, forceCloseOpened = false): void { - options = options ?? {}; + private openWSConnection(options: ClientOptions & ClientRequestArgs = this.stationInfo.wsOptions, forceCloseOpened = false): void { options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000; if (!Utils.isNullOrUndefined(this.stationInfo.supervisionUser) && !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)) { options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`; @@ -960,13 +1030,13 @@ export default class ChargingStation { if (this.isWebSocketConnectionOpened() && forceCloseOpened) { this.wsConnection.close(); } - let protocol; - switch (this.getOCPPVersion()) { + let protocol: string; + switch (this.getOcppVersion()) { case OCPPVersion.VERSION_16: protocol = 'ocpp' + OCPPVersion.VERSION_16; break; default: - this.handleUnsupportedVersion(this.getOCPPVersion()); + this.handleUnsupportedVersion(this.getOcppVersion()); break; } this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options); @@ -995,7 +1065,7 @@ export default class ChargingStation { } }); } catch (error) { - FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error); + FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException); } } else { logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile + '. Not monitoring changes'); @@ -1028,7 +1098,7 @@ export default class ChargingStation { } }); } catch (error) { - FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error); + FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error as NodeJS.ErrnoException); } } @@ -1051,19 +1121,21 @@ export default class ChargingStation { if (this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() || this.getAutoReconnectMaxRetries() === -1) { this.autoReconnectRetryCount++; const reconnectDelay = (this.getReconnectExponentialDelay() ? Utils.exponentialDelay(this.autoReconnectRetryCount) : this.getConnectionTimeout() * 1000); - const reconnectTimeout = reconnectDelay - 100; + const reconnectTimeout = (reconnectDelay - 100) > 0 && reconnectDelay; logger.error(`${this.logPrefix()} WebSocket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`); await Utils.sleep(reconnectDelay); logger.error(this.logPrefix() + ' WebSocket: reconnecting try #' + this.autoReconnectRetryCount.toString()); - this.openWSConnection({ handshakeTimeout: reconnectTimeout }, true); + this.openWSConnection({ ...this.stationInfo.wsOptions, handshakeTimeout: reconnectTimeout }, true); this.wsConnectionRestarted = true; } else if (this.getAutoReconnectMaxRetries() !== -1) { logger.error(`${this.logPrefix()} WebSocket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`); } } - private initTransactionAttributesOnConnector(connectorId: number): void { - this.getConnectorStatus(connectorId).authorized = false; + private initializeConnectorStatus(connectorId: number): void { + this.getConnectorStatus(connectorId).idTagLocalAuthorized = false; + this.getConnectorStatus(connectorId).idTagAuthorized = false; + this.getConnectorStatus(connectorId).transactionRemoteStarted = false; this.getConnectorStatus(connectorId).transactionStarted = false; this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0; this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;