+ 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 = new OCPP16IncomingRequestService(this);
+ this.ocppRequestService = new OCPP16RequestService(this, new OCPP16ResponseService(this));
+ break;
+ default:
+ this.handleUnsupportedVersion(this.getOcppVersion());
+ break;
+ }
+ // OCPP parameters
+ this.initOcppParameters();
+ if (this.stationInfo.autoRegister) {
+ this.bootNotificationResponse = {
+ currentTime: new Date().toISOString(),
+ interval: this.getHeartbeatInterval() / 1000,
+ status: RegistrationStatus.ACCEPTED
+ };
+ }
+ this.stationInfo.powerDivider = this.getPowerDivider();
+ if (this.getEnableStatistics()) {
+ this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId, this.wsConnectionUrl);
+ }
+ }
+
+ 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(), { readonly: true });
+ if (!this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData)) {
+ this.addConfigurationKey(StandardParametersKey.MeterValuesSampledData, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER);
+ }
+ if (!this.getConfigurationKey(StandardParametersKey.ConnectorPhaseRotation)) {
+ const connectorPhaseRotation = [];
+ for (const connectorId of this.connectors.keys()) {
+ // AC/DC
+ if (connectorId === 0 && this.getNumberOfPhases() === 0) {
+ connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
+ } else if (connectorId > 0 && this.getNumberOfPhases() === 0) {
+ connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
+ // AC
+ } else if (connectorId > 0 && this.getNumberOfPhases() === 1) {
+ connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.NotApplicable}`);
+ } else if (connectorId > 0 && this.getNumberOfPhases() === 3) {
+ connectorPhaseRotation.push(`${connectorId}.${ConnectorPhaseRotation.RST}`);
+ }
+ }
+ this.addConfigurationKey(StandardParametersKey.ConnectorPhaseRotation, connectorPhaseRotation.toString());
+ }
+ if (!this.getConfigurationKey(StandardParametersKey.AuthorizeRemoteTxRequests)) {
+ this.addConfigurationKey(StandardParametersKey.AuthorizeRemoteTxRequests, 'true');
+ }
+ if (!this.getConfigurationKey(StandardParametersKey.LocalAuthListEnabled)
+ && this.getConfigurationKey(StandardParametersKey.SupportedFeatureProfiles).value.includes(SupportedFeatureProfiles.Local_Auth_List_Management)) {
+ this.addConfigurationKey(StandardParametersKey.LocalAuthListEnabled, 'false');
+ }
+ if (!this.getConfigurationKey(StandardParametersKey.ConnectionTimeOut)) {
+ this.addConfigurationKey(StandardParametersKey.ConnectionTimeOut, Constants.DEFAULT_CONNECTION_TIMEOUT.toString());
+ }