+ public saveOcppConfiguration(): void {
+ if (this.getOcppPersistentConfiguration()) {
+ this.saveConfiguration();
+ }
+ }
+
+ public getChargingProfilePowerLimit(connectorId: number): number | undefined {
+ let limit: number, matchingChargingProfile: ChargingProfile;
+ let chargingProfiles: ChargingProfile[] = [];
+ // Get charging profiles for connector and sort by stack level
+ chargingProfiles = this.getConnectorStatus(connectorId).chargingProfiles.sort(
+ (a, b) => b.stackLevel - a.stackLevel
+ );
+ // Get profiles on connector 0
+ if (this.getConnectorStatus(0).chargingProfiles) {
+ chargingProfiles.push(
+ ...this.getConnectorStatus(0).chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel)
+ );
+ }
+ if (!Utils.isEmptyArray(chargingProfiles)) {
+ const result = ChargingStationUtils.getLimitFromChargingProfiles(
+ chargingProfiles,
+ this.logPrefix()
+ );
+ if (!Utils.isNullOrUndefined(result)) {
+ limit = result.limit;
+ matchingChargingProfile = result.matchingChargingProfile;
+ switch (this.getCurrentOutType()) {
+ case CurrentType.AC:
+ limit =
+ matchingChargingProfile.chargingSchedule.chargingRateUnit ===
+ ChargingRateUnitType.WATT
+ ? limit
+ : ACElectricUtils.powerTotal(this.getNumberOfPhases(), this.getVoltageOut(), limit);
+ break;
+ case CurrentType.DC:
+ limit =
+ matchingChargingProfile.chargingSchedule.chargingRateUnit ===
+ ChargingRateUnitType.WATT
+ ? limit
+ : DCElectricUtils.power(this.getVoltageOut(), limit);
+ }
+
+ const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
+ if (limit > connectorMaximumPower) {
+ logger.error(
+ `${this.logPrefix()} Charging profile id ${
+ matchingChargingProfile.chargingProfileId
+ } limit is greater than connector id ${connectorId} maximum, dump charging profiles' stack: %j`,
+ this.getConnectorStatus(connectorId).chargingProfiles
+ );
+ limit = connectorMaximumPower;
+ }
+ }
+ }
+ return limit;
+ }
+
+ public setChargingProfile(connectorId: number, cp: ChargingProfile): void {
+ if (Utils.isNullOrUndefined(this.getConnectorStatus(connectorId).chargingProfiles)) {
+ logger.error(
+ `${this.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
+ );
+ this.getConnectorStatus(connectorId).chargingProfiles = [];
+ }
+ if (Array.isArray(this.getConnectorStatus(connectorId).chargingProfiles) === false) {
+ logger.error(
+ `${this.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
+ );
+ this.getConnectorStatus(connectorId).chargingProfiles = [];
+ }
+ 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 hasFeatureProfile(featureProfile: SupportedFeatureProfiles) {
+ return ChargingStationConfigurationUtils.getConfigurationKey(
+ this,
+ StandardParametersKey.SupportedFeatureProfiles
+ )?.value.includes(featureProfile);
+ }
+
+ public bufferMessage(message: string): void {
+ this.messageBuffer.add(message);
+ }
+
+ public openWSConnection(
+ options: WsOptions = this.stationInfo?.wsOptions ?? {},
+ params: { closeOpened?: boolean; terminateOpened?: boolean } = {
+ closeOpened: false,
+ terminateOpened: false,
+ }
+ ): void {
+ options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
+ params.closeOpened = params?.closeOpened ?? false;
+ params.terminateOpened = params?.terminateOpened ?? false;
+ if (
+ !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
+ !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
+ ) {
+ options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
+ }
+ if (params?.closeOpened) {
+ this.closeWSConnection();
+ }
+ if (params?.terminateOpened) {
+ this.terminateWSConnection();
+ }
+ let protocol: string;
+ switch (this.getOcppVersion()) {
+ case OCPPVersion.VERSION_16:
+ protocol = 'ocpp' + OCPPVersion.VERSION_16;
+ break;
+ default:
+ this.handleUnsupportedVersion(this.getOcppVersion());
+ break;
+ }
+
+ if (this.isWebSocketConnectionOpened()) {
+ logger.warn(
+ `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`
+ );
+ return;
+ }
+
+ logger.info(
+ `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
+ );
+
+ this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
+
+ // Handle WebSocket message
+ this.wsConnection.on(
+ 'message',
+ this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void
+ );
+ // Handle WebSocket error
+ this.wsConnection.on(
+ 'error',
+ this.onError.bind(this) as (this: WebSocket, error: Error) => void
+ );
+ // Handle WebSocket close
+ this.wsConnection.on(
+ 'close',
+ this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void
+ );
+ // Handle WebSocket open
+ this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void);
+ // Handle WebSocket ping
+ this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void);
+ // Handle WebSocket pong
+ this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void);
+ }
+
+ public closeWSConnection(): void {
+ if (this.isWebSocketConnectionOpened()) {
+ this.wsConnection.close();
+ this.wsConnection = null;
+ }
+ }
+
+ public startAutomaticTransactionGenerator(connectorIds?: number[]): void {
+ if (!this.automaticTransactionGenerator) {
+ this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(
+ this.getAutomaticTransactionGeneratorConfigurationFromTemplate(),
+ this
+ );
+ }
+ if (!Utils.isEmptyArray(connectorIds)) {
+ for (const connectorId of connectorIds) {
+ this.automaticTransactionGenerator.startConnector(connectorId);