+ public setChargingProfile(connectorId: number, cp: ChargingProfile): void {
+ let cpReplaced = false;
+ if (!Utils.isEmptyArray(this.getConnectorStatus(connectorId).chargingProfiles)) {
+ this.getConnectorStatus(connectorId).chargingProfiles?.forEach((chargingProfile: ChargingProfile, index: number) => {
+ if (chargingProfile.chargingProfileId === cp.chargingProfileId
+ || (chargingProfile.stackLevel === cp.stackLevel && chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)) {
+ this.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
+ cpReplaced = true;
+ }
+ });
+ }
+ !cpReplaced && this.getConnectorStatus(connectorId).chargingProfiles?.push(cp);
+ }
+
+ 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;
+ this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
+ delete this.getConnectorStatus(connectorId).transactionBeginMeterValue;
+ this.stopMeterValues(connectorId);
+ }
+
+ public bufferMessage(message: string): void {
+ this.messageBuffer.add(message);
+ }
+
+ 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;
+ const idSuffix = stationTemplate.nameSuffix ?? '';
+ return stationTemplate.fixedName ? stationTemplate.baseName : stationTemplate.baseName + '-' + instanceIndex.toString() + ('000000000' + this.index.toString()).substr(('000000000' + this.index.toString()).length - 4) + idSuffix;
+ }
+
+ private buildStationInfo(): ChargingStationInfo {
+ let stationTemplateFromFile: ChargingStationTemplate;
+ try {
+ // Load template file
+ const fileDescriptor = fs.openSync(this.stationTemplateFile, 'r');
+ stationTemplateFromFile = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8')) as ChargingStationTemplate;
+ fs.closeSync(fileDescriptor);
+ } 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)) {
+ stationTemplateFromFile.power = stationTemplateFromFile.power as number[];
+ const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplateFromFile.power.length);
+ stationInfo.maxPower = stationTemplateFromFile.powerUnit === PowerUnits.KILO_WATT
+ ? stationTemplateFromFile.power[powerArrayRandomIndex] * 1000
+ : stationTemplateFromFile.power[powerArrayRandomIndex];
+ } else {
+ stationTemplateFromFile.power = stationTemplateFromFile.power as number;
+ stationInfo.maxPower = stationTemplateFromFile.powerUnit === PowerUnits.KILO_WATT
+ ? stationTemplateFromFile.power * 1000
+ : stationTemplateFromFile.power;
+ }
+ delete stationInfo.power;
+ delete stationInfo.powerUnit;
+ stationInfo.chargingStationId = chargingStationId;
+ stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
+ return stationInfo;
+ }
+
+ private getOcppVersion(): OCPPVersion {
+ return this.stationInfo.ocppVersion ? this.stationInfo.ocppVersion : OCPPVersion.VERSION_16;
+ }
+
+ private handleUnsupportedVersion(version: OCPPVersion) {
+ const errMsg = `${this.logPrefix()} Unsupported protocol version '${version}' configured in template file ${this.stationTemplateFile}`;
+ logger.error(errMsg);
+ throw new Error(errMsg);
+ }
+
+ 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 },
+ };
+ // Build connectors if needed
+ const maxConnectors = this.getMaxNumberOfConnectors();
+ if (maxConnectors <= 0) {
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with ${maxConnectors} connectors`);
+ }
+ const templateMaxConnectors = this.getTemplateMaxNumberOfConnectors();
+ if (templateMaxConnectors <= 0) {
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector configuration`);
+ }
+ if (!this.stationInfo.Connectors[0]) {
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector Id 0 configuration`);
+ }
+ // Sanity check
+ if (maxConnectors > (this.stationInfo.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) && !this.stationInfo.randomConnectors) {
+ logger.warn(`${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${this.stationTemplateFile}, forcing random connector configurations affectation`);
+ 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?.size === 0 || connectorsConfigChanged) {
+ connectorsConfigChanged && (this.connectors.clear());
+ this.connectorsConfigurationHash = connectorsConfigHash;
+ // Add connector Id 0
+ let lastConnector = '0';
+ for (lastConnector in this.stationInfo.Connectors) {
+ const lastConnectorId = Utils.convertToInt(lastConnector);
+ if (lastConnectorId === 0 && this.getUseConnectorId0() && this.stationInfo.Connectors[lastConnector]) {
+ this.connectors.set(lastConnectorId, Utils.cloneObject<ConnectorStatus>(this.stationInfo.Connectors[lastConnector]));
+ this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.OPERATIVE;
+ if (Utils.isUndefined(this.getConnectorStatus(lastConnectorId)?.chargingProfiles)) {
+ this.getConnectorStatus(lastConnectorId).chargingProfiles = [];
+ }
+ }
+ }
+ // Generate all connectors
+ if ((this.stationInfo.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) > 0) {
+ for (let index = 1; index <= maxConnectors; index++) {
+ const randConnectorId = this.stationInfo.randomConnectors ? Utils.getRandomInteger(Utils.convertToInt(lastConnector), 1) : index;
+ this.connectors.set(index, Utils.cloneObject<ConnectorStatus>(this.stationInfo.Connectors[randConnectorId]));
+ this.getConnectorStatus(index).availability = AvailabilityType.OPERATIVE;
+ if (Utils.isUndefined(this.getConnectorStatus(index)?.chargingProfiles)) {
+ this.getConnectorStatus(index).chargingProfiles = [];
+ }
+ }
+ }
+ }
+ // Avoid duplication of connectors related information
+ delete this.stationInfo.Connectors;
+ // Initialize transaction attributes on connectors
+ for (const connectorId of this.connectors.keys()) {
+ if (connectorId > 0 && !this.getConnectorStatus(connectorId)?.transactionStarted) {
+ this.initializeConnectorStatus(connectorId);
+ }
+ }
+ this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionUrl().href + '/' + this.stationInfo.chargingStationId);
+ switch (this.getOcppVersion()) {
+ case OCPPVersion.VERSION_16:
+ this.ocppIncomingRequestService = OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>(this);
+ this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(this, OCPP16ResponseService.getInstance<OCPP16ResponseService>(this));