X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=018a6fbeea98fa780b522a2d08c1218011aa6c7f;hb=16cd35ad01b48377d39077d8b30e6c2055a5f8ea;hp=c50d2ad9ba3bf104f14b64aa4a7770698d95d785;hpb=ee0f106b09f38b1561d970ea4377eb199389298e;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index c50d2ad9..018a6fbe 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -5,7 +5,6 @@ import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Resp import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration'; import ChargingStationTemplate, { CurrentType, PowerUnits, Voltage } from '../types/ChargingStationTemplate'; 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'; import WebSocket, { ClientOptions, Data, OPEN } from 'ws'; @@ -17,6 +16,7 @@ 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'; @@ -24,12 +24,14 @@ 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'; @@ -79,7 +81,7 @@ export default class ChargingStation { } get wsConnectionUrl(): URL { - return this.getSupervisionURLOCPPConfiguration() ? new URL(this.getConfigurationKey(this.stationInfo.supervisionURLOCPPKey ?? VendorDefaultParametersKey.ConnectionUrl).value + '/' + this.stationInfo.chargingStationId) : this.wsConfiguredConnectionUrl; + return this.getSupervisionUrlOcppConfiguration() ? new URL(this.getConfigurationKey(this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl).value + '/' + this.stationInfo.chargingStationId) : this.wsConfiguredConnectionUrl; } public logPrefix(): string { @@ -117,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 { @@ -439,8 +453,8 @@ export default class ChargingStation { } } - private getSupervisionURLOCPPConfiguration(): boolean { - return this.stationInfo.supervisionURLOCPPConfiguration ?? false; + private getSupervisionUrlOcppConfiguration(): boolean { + return this.stationInfo.supervisionUrlOcppConfiguration ?? false; } private getChargingStationId(stationTemplate: ChargingStationTemplate): string { @@ -460,6 +474,10 @@ export default class ChargingStation { } catch (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)) { @@ -476,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; } @@ -555,18 +573,18 @@ export default class ChargingStation { this.initializeConnectorStatus(connectorId); } } - this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionURL().href + '/' + this.stationInfo.chargingStationId); - 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(), @@ -580,9 +598,9 @@ 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 }); + 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}`); @@ -634,12 +652,26 @@ 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.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 this.startMessageSequence(); + this.stopped && (this.stopped = false); + if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) { + this.flushMessageBuffer(); + } + await Utils.sleep(Constants.CHARGING_STATION_DEFAULT_START_SEQUENCE_DELAY); + } } else { logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`); } @@ -829,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 @@ -928,17 +960,45 @@ export default class ChargingStation { } } - private getConfiguredSupervisionURL(): URL { - const supervisionUrls = Utils.cloneObject(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); } @@ -971,12 +1031,12 @@ export default class ChargingStation { this.wsConnection.close(); } let protocol: string; - switch (this.getOCPPVersion()) { + 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);