- private _index: number;
- private _stationTemplateFile: string;
- private _stationInfo: ChargingStationInfo;
- private _bootNotificationRequest: BootNotificationRequest;
- private _bootNotificationResponse: BootNotificationResponse;
- private _connectors: Connectors;
- private _configuration: ChargingStationConfiguration;
- private _connectorsConfigurationHash: string;
- private _supervisionUrl: string;
- private _wsConnectionUrl: string;
- private _wsConnection: WebSocket;
- private _hasStopped: boolean;
- private _hasSocketRestarted: boolean;
- private _autoReconnectRetryCount: number;
- private _requests: Requests;
- private _messageQueue: string[];
- private _automaticTransactionGeneration: AutomaticTransactionGenerator;
- private _authorizedTags: string[];
- private _heartbeatInterval: number;
- private _heartbeatSetInterval: NodeJS.Timeout;
- private _webSocketPingSetInterval: NodeJS.Timeout;
- private _statistics: Statistics;
- private _performanceObserver: PerformanceObserver;
-
- constructor(index: number, stationTemplateFile: string) {
- this._index = index;
- this._stationTemplateFile = stationTemplateFile;
- this._connectors = {} as Connectors;
- this._initialize();
-
- this._hasStopped = false;
- this._hasSocketRestarted = false;
- this._autoReconnectRetryCount = 0;
-
- this._requests = {} as Requests;
- this._messageQueue = [] as string[];
-
- this._authorizedTags = this._loadAndGetAuthorizedTags();
- }
-
- _getStationName(stationTemplate: ChargingStationTemplate): string {
- return stationTemplate.fixedName ? stationTemplate.baseName : stationTemplate.baseName + '-' + ('000000000' + this._index.toString()).substr(('000000000' + this._index.toString()).length - 4);
- }
-
- _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) {
- logger.error('Template file ' + this._stationTemplateFile + ' loading error: %j', error);
- throw error;
- }
- const stationInfo: ChargingStationInfo = stationTemplateFromFile || {} as ChargingStationInfo;
- if (!Utils.isEmptyArray(stationTemplateFromFile.power)) {
- stationTemplateFromFile.power = stationTemplateFromFile.power as number[];
- stationInfo.maxPower = stationTemplateFromFile.power[Math.floor(Math.random() * stationTemplateFromFile.power.length)];
- } else {
- stationInfo.maxPower = stationTemplateFromFile.power as number;
- }
- stationInfo.name = this._getStationName(stationTemplateFromFile);
- stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
- return stationInfo;
- }
-
- get stationInfo(): ChargingStationInfo {
- return this._stationInfo;
- }
-
- _initialize(): void {
- this._stationInfo = this._buildStationInfo();
- 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._supervisionUrl = this._getSupervisionURL();
- this._wsConnectionUrl = this._supervisionUrl + '/' + this._stationInfo.name;
- // 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');
- // FIXME: Handle shrinking the number of connectors
- if (!this._connectors || (this._connectors && this._connectorsConfigurationHash !== connectorsConfigHash)) {
- this._connectorsConfigurationHash = connectorsConfigHash;
- // Add connector Id 0
- let lastConnector = '0';
- for (lastConnector in this._stationInfo.Connectors) {
- if (Utils.convertToInt(lastConnector) === 0 && this._getUseConnectorId0() && this._stationInfo.Connectors[lastConnector]) {
- this._connectors[lastConnector] = Utils.cloneObject<Connector>(this._stationInfo.Connectors[lastConnector]);
- }
- }
- // 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.getRandomInt(Utils.convertToInt(lastConnector), 1) : index;
- this._connectors[index] = Utils.cloneObject<Connector>(this._stationInfo.Connectors[randConnectorID]);
- }
- }
- }
- // Avoid duplication of connectors related information
- delete this._stationInfo.Connectors;
- // Initialize transaction attributes on connectors
- for (const connector in this._connectors) {
- if (Utils.convertToInt(connector) > 0 && !this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
- this._initTransactionOnConnector(Utils.convertToInt(connector));
- }
- }
- // OCPP parameters
- this._addConfigurationKey(StandardParametersKey.NumberOfConnectors, this._getNumberOfConnectors().toString(), true);
- if (!this._getConfigurationKey(StandardParametersKey.MeterValuesSampledData)) {
- this._addConfigurationKey(StandardParametersKey.MeterValuesSampledData, MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER);
- }
- this._stationInfo.powerDivider = this._getPowerDivider();
- if (this.getEnableStatistics()) {
- this._statistics = Statistics.getInstance();
- this._statistics.objName = this._stationInfo.name;
- this._performanceObserver = new PerformanceObserver((list) => {
- const entry = list.getEntries()[0];
- this._statistics.logPerformance(entry, Constants.ENTITY_CHARGING_STATION);
- this._performanceObserver.disconnect();
- });
+ public readonly templateFile: string;
+ public stationInfo!: ChargingStationInfo;
+ public stopped: boolean;
+ public authorizedTagsCache: AuthorizedTagsCache;
+ public automaticTransactionGenerator!: AutomaticTransactionGenerator;
+ public ocppConfiguration!: ChargingStationOcppConfiguration;
+ public wsConnection!: WebSocket;
+ public readonly connectors: Map<number, ConnectorStatus>;
+ public readonly requests: Map<string, CachedRequest>;
+ public performanceStatistics!: PerformanceStatistics;
+ public heartbeatSetInterval!: NodeJS.Timeout;
+ public ocppRequestService!: OCPPRequestService;
+ public bootNotificationRequest!: BootNotificationRequest;
+ public bootNotificationResponse!: BootNotificationResponse | null;
+ public powerDivider!: number;
+ private readonly index: number;
+ private configurationFile!: string;
+ private configurationFileHash!: string;
+ private connectorsConfigurationHash!: string;
+ private ocppIncomingRequestService!: OCPPIncomingRequestService;
+ private readonly messageBuffer: Set<string>;
+ private configuredSupervisionUrl!: URL;
+ private wsConnectionRestarted: boolean;
+ private autoReconnectRetryCount: number;
+ private templateFileWatcher!: fs.FSWatcher;
+ private readonly sharedLRUCache: SharedLRUCache;
+ private webSocketPingSetInterval!: NodeJS.Timeout;
+ private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
+
+ constructor(index: number, templateFile: string) {
+ this.index = index;
+ this.templateFile = templateFile;
+ this.connectors = new Map<number, ConnectorStatus>();
+ this.requests = new Map<string, CachedRequest>();
+ this.messageBuffer = new Set<string>();
+ this.sharedLRUCache = SharedLRUCache.getInstance();
+ this.authorizedTagsCache = AuthorizedTagsCache.getInstance();
+ this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
+ this.stopped = false;
+ this.wsConnectionRestarted = false;
+ this.autoReconnectRetryCount = 0;
+
+ this.initialize();
+ }
+
+ private get wsConnectionUrl(): URL {
+ return new URL(
+ (this.getSupervisionUrlOcppConfiguration()
+ ? ChargingStationConfigurationUtils.getConfigurationKey(
+ this,
+ this.getSupervisionUrlOcppKey()
+ ).value
+ : this.configuredSupervisionUrl.href) +
+ '/' +
+ this.stationInfo.chargingStationId
+ );
+ }
+
+ public logPrefix(): string {
+ return Utils.logPrefix(
+ ` ${
+ this?.stationInfo?.chargingStationId ??
+ ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())
+ } |`
+ );
+ }
+
+ public getRandomIdTag(): string {
+ const authorizationFile = ChargingStationUtils.getAuthorizationFile(this.stationInfo);
+ const index = Math.floor(
+ Utils.secureRandom() * this.authorizedTagsCache.getAuthorizedTags(authorizationFile).length
+ );
+ return this.authorizedTagsCache.getAuthorizedTags(authorizationFile)[index];
+ }
+
+ public hasAuthorizedTags(): boolean {
+ return !Utils.isEmptyArray(
+ this.authorizedTagsCache.getAuthorizedTags(
+ ChargingStationUtils.getAuthorizationFile(this.stationInfo)
+ )
+ );
+ }
+
+ public getEnableStatistics(): boolean | undefined {
+ return !Utils.isUndefined(this.stationInfo.enableStatistics)
+ ? this.stationInfo.enableStatistics
+ : true;
+ }
+
+ public getMustAuthorizeAtRemoteStart(): boolean | undefined {
+ return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
+ }
+
+ public getPayloadSchemaValidation(): boolean | undefined {
+ return this.stationInfo.payloadSchemaValidation ?? true;
+ }
+
+ public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
+ const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
+ switch (this.getCurrentOutType(stationInfo)) {
+ case CurrentType.AC:
+ return !Utils.isUndefined(localStationInfo.numberOfPhases)
+ ? localStationInfo.numberOfPhases
+ : 3;
+ case CurrentType.DC:
+ return 0;