X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=c50d2ad9ba3bf104f14b64aa4a7770698d95d785;hb=4198ad5c76ad58e444c1dd2d3f81b5f8fd2846d4;hp=dde845621781534c7d48ed366fd75b06ec9d1490;hpb=8e24227340572ec9f086d966c079ead58cc60ef5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index dde84562..c50d2ad9 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -4,7 +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 { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles, VendorDefaultParametersKey } from '../types/ocpp/Configuration'; import { ConnectorStatus, SampledValueTemplate } from '../types/Connectors'; import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues'; import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket'; @@ -14,6 +14,7 @@ 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 Constants from '../utils/Constants'; @@ -34,6 +35,7 @@ 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 { @@ -53,7 +55,7 @@ export default class ChargingStation { private connectorsConfigurationHash!: string; private ocppIncomingRequestService!: OCPPIncomingRequestService; private readonly messageBuffer: Set; - private wsConnectionUrl!: URL; + private wsConfiguredConnectionUrl!: URL; private wsConnectionRestarted: boolean; private stopped: boolean; private autoReconnectRetryCount: number; @@ -76,6 +78,10 @@ export default class ChargingStation { 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} |`); } @@ -331,6 +337,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 { @@ -349,6 +356,7 @@ export default class ChargingStation { this.performanceStatistics.stop(); } this.bootNotificationResponse = null; + parentPort.postMessage({ id: ChargingStationWorkerMessageEvents.STOPPED, data: { id: this.stationInfo.chargingStationId } }); this.stopped = true; } @@ -361,8 +369,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, @@ -400,9 +411,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; @@ -425,6 +439,10 @@ export default class ChargingStation { } } + 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; @@ -440,9 +458,10 @@ 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 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); @@ -474,14 +493,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) { @@ -533,9 +552,10 @@ 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); } } + this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionURL().href + '/' + this.stationInfo.chargingStationId); switch (this.getOCPPVersion()) { case OCPPVersion.VERSION_16: this.ocppIncomingRequestService = new OCPP16IncomingRequestService(this); @@ -561,10 +581,13 @@ export default class ChargingStation { } 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); } @@ -703,9 +726,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); } } @@ -744,7 +767,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); @@ -905,8 +928,8 @@ export default class ChargingStation { } } - private getSupervisionURL(): URL { - const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionURL ? this.stationInfo.supervisionURL : Configuration.getSupervisionURLs()); + private getConfiguredSupervisionURL(): URL { + const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionURL ?? Configuration.getSupervisionURLs()); let indexUrl = 0; if (!Utils.isEmptyArray(supervisionUrls)) { if (Configuration.getDistributeStationsToTenantsEqually()) { @@ -939,8 +962,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}`; @@ -948,7 +970,7 @@ export default class ChargingStation { if (this.isWebSocketConnectionOpened() && forceCloseOpened) { this.wsConnection.close(); } - let protocol; + let protocol: string; switch (this.getOCPPVersion()) { case OCPPVersion.VERSION_16: protocol = 'ocpp' + OCPPVersion.VERSION_16; @@ -983,7 +1005,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'); @@ -1016,7 +1038,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); } } @@ -1039,19 +1061,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;