perf: reduce OCPPUtils memory usage
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 4 Dec 2023 21:35:09 +0000 (22:35 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 4 Dec 2023 21:35:09 +0000 (22:35 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/index.ts

index d76766cc68f5d3e35187a22d265e88ccd5b8e6a9..12cf7ee4b3fdd495f1dd8bbbbb9b72033e1abbac 100644 (file)
@@ -7,7 +7,7 @@ import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns';
 import type { ChargingStation } from './ChargingStation';
 import { checkChargingStation } from './Helpers';
 import { IdTagsCache } from './IdTagsCache';
-import { OCPPServiceUtils } from './ocpp';
+import { isIdTagAuthorized } from './ocpp';
 import { BaseError } from '../exception';
 import { PerformanceStatistics } from '../performance';
 import {
@@ -431,7 +431,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
       )} start transaction with an idTag '${idTag}'`;
       if (this.getRequireAuthorize()) {
         ++this.connectorsStatus.get(connectorId)!.authorizeRequests!;
-        if (await OCPPServiceUtils.isIdTagAuthorized(this.chargingStation, connectorId, idTag)) {
+        if (await isIdTagAuthorized(this.chargingStation, connectorId, idTag)) {
           ++this.connectorsStatus.get(connectorId)!.acceptedAuthorizeRequests!;
           logger.info(startTransactionLogMsg);
           // Start transaction
index aa251e395be27922cea3bb87bedeec384b12701e..5860a654e47afc9486653cc9ab1f4f069375f9b6 100644 (file)
@@ -55,7 +55,9 @@ import {
   OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
-  OCPPServiceUtils,
+  buildStatusNotificationRequest,
+  getMessageTypeString,
+  sendAndSetConnectorStatus,
 } from './ocpp';
 import { SharedLRUCache } from './SharedLRUCache';
 import { BaseError, OCPPError } from '../exception';
@@ -887,7 +889,7 @@ export class ChargingStation extends EventEmitter {
       await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING);
     }
     this.getConnectorStatus(reservation.connectorId)!.reservation = reservation;
-    await OCPPServiceUtils.sendAndSetConnectorStatus(
+    await sendAndSetConnectorStatus(
       this,
       reservation.connectorId,
       ConnectorStatusEnum.Reserved,
@@ -909,7 +911,7 @@ export class ChargingStation extends EventEmitter {
       case ReservationTerminationReason.RESERVATION_CANCELED:
       case ReservationTerminationReason.REPLACE_EXISTING:
       case ReservationTerminationReason.EXPIRED:
-        await OCPPServiceUtils.sendAndSetConnectorStatus(
+        await sendAndSetConnectorStatus(
           this,
           reservation.connectorId,
           ConnectorStatusEnum.Available,
@@ -1027,11 +1029,18 @@ export class ChargingStation extends EventEmitter {
           isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!);
           if (isNullOrUndefined(error)) {
             logger.debug(
-              `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
                 messageType,
-              )} payload sent: ${message}`,
+              )} OCPP message sent '${JSON.stringify(message)}'`,
             );
             this.messageBuffer.delete(message);
+          } else {
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
+                messageType,
+              )} OCPP message '${JSON.stringify(message)}' send failed:`,
+              error,
+            );
           }
         });
       }
@@ -1816,7 +1825,7 @@ export class ChargingStation extends EventEmitter {
     }
     throw new OCPPError(
       ErrorType.PROTOCOL_ERROR,
-      `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
+      `Cached request for message id ${messageId} ${getMessageTypeString(
         messageType,
       )} is not an array`,
       undefined,
@@ -2103,12 +2112,7 @@ export class ChargingStation extends EventEmitter {
         if (evseId > 0) {
           for (const [connectorId, connectorStatus] of evseStatus.connectors) {
             const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus);
-            await OCPPServiceUtils.sendAndSetConnectorStatus(
-              this,
-              connectorId,
-              connectorBootStatus,
-              evseId,
-            );
+            await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId);
           }
         }
       }
@@ -2120,7 +2124,7 @@ export class ChargingStation extends EventEmitter {
             connectorId,
             this.getConnectorStatus(connectorId)!,
           );
-          await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
+          await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus);
         }
       }
     }
@@ -2165,7 +2169,7 @@ export class ChargingStation extends EventEmitter {
             >(
               this,
               RequestCommand.STATUS_NOTIFICATION,
-              OCPPServiceUtils.buildStatusNotificationRequest(
+              buildStatusNotificationRequest(
                 this,
                 connectorId,
                 ConnectorStatusEnum.Unavailable,
@@ -2185,11 +2189,7 @@ export class ChargingStation extends EventEmitter {
           >(
             this,
             RequestCommand.STATUS_NOTIFICATION,
-            OCPPServiceUtils.buildStatusNotificationRequest(
-              this,
-              connectorId,
-              ConnectorStatusEnum.Unavailable,
-            ),
+            buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable),
           );
           delete this.getConnectorStatus(connectorId)?.status;
         }
index f62344639c91266c2b2855d2b4fb8d4c6075072d..8a0b2680632c35c6bb235ff6eb6eb93c800505a5 100644 (file)
@@ -45,7 +45,187 @@ import {
   min,
 } from '../../utils';
 
+export const getMessageTypeString = (messageType: MessageType): string => {
+  switch (messageType) {
+    case MessageType.CALL_MESSAGE:
+      return 'request';
+    case MessageType.CALL_RESULT_MESSAGE:
+      return 'response';
+    case MessageType.CALL_ERROR_MESSAGE:
+      return 'error';
+    default:
+      return 'unknown';
+  }
+};
+
+export const buildStatusNotificationRequest = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  status: ConnectorStatusEnum,
+  evseId?: number,
+): StatusNotificationRequest => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      return {
+        connectorId,
+        status,
+        errorCode: ChargePointErrorCode.NO_ERROR,
+      } as OCPP16StatusNotificationRequest;
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      return {
+        timestamp: new Date(),
+        connectorStatus: status,
+        connectorId,
+        evseId,
+      } as OCPP20StatusNotificationRequest;
+    default:
+      throw new BaseError('Cannot build status notification payload: OCPP version not supported');
+  }
+};
+
+export const isIdTagAuthorized = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  idTag: string,
+): Promise<boolean> => {
+  if (
+    !chargingStation.getLocalAuthListEnabled() &&
+    !chargingStation.stationInfo?.remoteAuthorization
+  ) {
+    logger.warn(
+      `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`,
+    );
+  }
+  if (
+    chargingStation.getLocalAuthListEnabled() === true &&
+    isIdTagLocalAuthorized(chargingStation, idTag)
+  ) {
+    const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!;
+    connectorStatus.localAuthorizeIdTag = idTag;
+    connectorStatus.idTagLocalAuthorized = true;
+    return true;
+  } else if (chargingStation.stationInfo?.remoteAuthorization) {
+    return await isIdTagRemoteAuthorized(chargingStation, connectorId, idTag);
+  }
+  return false;
+};
+
+const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string): boolean => {
+  return (
+    chargingStation.hasIdTags() === true &&
+    isNotEmptyString(
+      chargingStation.idTagsCache
+        .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
+        ?.find((tag) => tag === idTag),
+    )
+  );
+};
+
+const isIdTagRemoteAuthorized = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  idTag: string,
+): Promise<boolean> => {
+  chargingStation.getConnectorStatus(connectorId)!.authorizeIdTag = idTag;
+  return (
+    (
+      await chargingStation.ocppRequestService.requestHandler<AuthorizeRequest, AuthorizeResponse>(
+        chargingStation,
+        RequestCommand.AUTHORIZE,
+        {
+          idTag,
+        },
+      )
+    )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
+  );
+};
+
+export const sendAndSetConnectorStatus = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  status: ConnectorStatusEnum,
+  evseId?: number,
+  options?: { send: boolean },
+): Promise<void> => {
+  options = { send: true, ...options };
+  if (options.send) {
+    checkConnectorStatusTransition(chargingStation, connectorId, status);
+    await chargingStation.ocppRequestService.requestHandler<
+      StatusNotificationRequest,
+      StatusNotificationResponse
+    >(
+      chargingStation,
+      RequestCommand.STATUS_NOTIFICATION,
+      buildStatusNotificationRequest(chargingStation, connectorId, status, evseId),
+    );
+  }
+  chargingStation.getConnectorStatus(connectorId)!.status = status;
+  chargingStation.emit(ChargingStationEvents.connectorStatusChanged, {
+    connectorId,
+    ...chargingStation.getConnectorStatus(connectorId),
+  });
+};
+
+const checkConnectorStatusTransition = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  status: ConnectorStatusEnum,
+): boolean => {
+  const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status;
+  let transitionAllowed = false;
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      if (
+        (connectorId === 0 &&
+          OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex(
+            (transition) => transition.from === fromStatus && transition.to === status,
+          ) !== -1) ||
+        (connectorId > 0 &&
+          OCPP16Constants.ChargePointStatusConnectorTransitions.findIndex(
+            (transition) => transition.from === fromStatus && transition.to === status,
+          ) !== -1)
+      ) {
+        transitionAllowed = true;
+      }
+      break;
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      if (
+        (connectorId === 0 &&
+          OCPP20Constants.ChargingStationStatusTransitions.findIndex(
+            (transition) => transition.from === fromStatus && transition.to === status,
+          ) !== -1) ||
+        (connectorId > 0 &&
+          OCPP20Constants.ConnectorStatusTransitions.findIndex(
+            (transition) => transition.from === fromStatus && transition.to === status,
+          ) !== -1)
+      ) {
+        transitionAllowed = true;
+      }
+      break;
+    default:
+      throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
+      );
+  }
+  if (transitionAllowed === false) {
+    logger.warn(
+      `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
+        ?.ocppVersion} connector id ${connectorId} status transition from '${
+        chargingStation.getConnectorStatus(connectorId)!.status
+      }' to '${status}' is not allowed`,
+    );
+  }
+  return transitionAllowed;
+};
+
 export class OCPPServiceUtils {
+  public static getMessageTypeString = getMessageTypeString;
+  public static sendAndSetConnectorStatus = sendAndSetConnectorStatus;
+  public static isIdTagAuthorized = isIdTagAuthorized;
+
   protected constructor() {
     // This is intentional
   }
@@ -68,19 +248,6 @@ export class OCPPServiceUtils {
     return ErrorType.FORMAT_VIOLATION;
   }
 
-  public static getMessageTypeString(messageType: MessageType): string {
-    switch (messageType) {
-      case MessageType.CALL_MESSAGE:
-        return 'request';
-      case MessageType.CALL_RESULT_MESSAGE:
-        return 'response';
-      case MessageType.CALL_ERROR_MESSAGE:
-        return 'error';
-      default:
-        return 'unknown';
-    }
-  }
-
   public static isRequestCommandSupported(
     chargingStation: ChargingStation,
     command: RequestCommand,
@@ -169,32 +336,6 @@ export class OCPPServiceUtils {
     }
   }
 
-  public static buildStatusNotificationRequest(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    status: ConnectorStatusEnum,
-    evseId?: number,
-  ): StatusNotificationRequest {
-    switch (chargingStation.stationInfo?.ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        return {
-          connectorId,
-          status,
-          errorCode: ChargePointErrorCode.NO_ERROR,
-        } as OCPP16StatusNotificationRequest;
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        return {
-          timestamp: new Date(),
-          connectorStatus: status,
-          connectorId,
-          evseId,
-        } as OCPP20StatusNotificationRequest;
-      default:
-        throw new BaseError('Cannot build status notification payload: OCPP version not supported');
-    }
-  }
-
   public static startHeartbeatInterval(chargingStation: ChargingStation, interval: number): void {
     if (chargingStation.heartbeatSetInterval === undefined) {
       chargingStation.startHeartbeat();
@@ -203,118 +344,6 @@ export class OCPPServiceUtils {
     }
   }
 
-  public static async sendAndSetConnectorStatus(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    status: ConnectorStatusEnum,
-    evseId?: number,
-    options?: { send: boolean },
-  ) {
-    options = { send: true, ...options };
-    if (options.send) {
-      OCPPServiceUtils.checkConnectorStatusTransition(chargingStation, connectorId, status);
-      await chargingStation.ocppRequestService.requestHandler<
-        StatusNotificationRequest,
-        StatusNotificationResponse
-      >(
-        chargingStation,
-        RequestCommand.STATUS_NOTIFICATION,
-        OCPPServiceUtils.buildStatusNotificationRequest(
-          chargingStation,
-          connectorId,
-          status,
-          evseId,
-        ),
-      );
-    }
-    chargingStation.getConnectorStatus(connectorId)!.status = status;
-    chargingStation.emit(ChargingStationEvents.connectorStatusChanged, {
-      connectorId,
-      ...chargingStation.getConnectorStatus(connectorId),
-    });
-  }
-
-  public static async isIdTagAuthorized(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    idTag: string,
-  ): Promise<boolean> {
-    if (
-      !chargingStation.getLocalAuthListEnabled() &&
-      !chargingStation.stationInfo?.remoteAuthorization
-    ) {
-      logger.warn(
-        `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`,
-      );
-    }
-    if (
-      chargingStation.getLocalAuthListEnabled() === true &&
-      OCPPServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)
-    ) {
-      const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!;
-      connectorStatus.localAuthorizeIdTag = idTag;
-      connectorStatus.idTagLocalAuthorized = true;
-      return true;
-    } else if (chargingStation.stationInfo?.remoteAuthorization) {
-      return await OCPPServiceUtils.isIdTagRemoteAuthorized(chargingStation, connectorId, idTag);
-    }
-    return false;
-  }
-
-  protected static checkConnectorStatusTransition(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    status: ConnectorStatusEnum,
-  ): boolean {
-    const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status;
-    let transitionAllowed = false;
-    switch (chargingStation.stationInfo?.ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        if (
-          (connectorId === 0 &&
-            OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex(
-              (transition) => transition.from === fromStatus && transition.to === status,
-            ) !== -1) ||
-          (connectorId > 0 &&
-            OCPP16Constants.ChargePointStatusConnectorTransitions.findIndex(
-              (transition) => transition.from === fromStatus && transition.to === status,
-            ) !== -1)
-        ) {
-          transitionAllowed = true;
-        }
-        break;
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        if (
-          (connectorId === 0 &&
-            OCPP20Constants.ChargingStationStatusTransitions.findIndex(
-              (transition) => transition.from === fromStatus && transition.to === status,
-            ) !== -1) ||
-          (connectorId > 0 &&
-            OCPP20Constants.ConnectorStatusTransitions.findIndex(
-              (transition) => transition.from === fromStatus && transition.to === status,
-            ) !== -1)
-        ) {
-          transitionAllowed = true;
-        }
-        break;
-      default:
-        throw new BaseError(
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
-        );
-    }
-    if (transitionAllowed === false) {
-      logger.warn(
-        `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
-          ?.ocppVersion} connector id ${connectorId} status transition from '${
-          chargingStation.getConnectorStatus(connectorId)!.status
-        }' to '${status}' is not allowed`,
-      );
-    }
-    return transitionAllowed;
-  }
-
   protected static parseJsonSchemaFile<T extends JsonType>(
     relativePath: string,
     ocppVersion: OCPPVersion,
@@ -441,35 +470,6 @@ export class OCPPServiceUtils {
     return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!;
   }
 
-  private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean {
-    return (
-      chargingStation.hasIdTags() === true &&
-      isNotEmptyString(
-        chargingStation.idTagsCache
-          .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
-          ?.find((tag) => tag === idTag),
-      )
-    );
-  }
-
-  private static async isIdTagRemoteAuthorized(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    idTag: string,
-  ): Promise<boolean> {
-    chargingStation.getConnectorStatus(connectorId)!.authorizeIdTag = idTag;
-    return (
-      (
-        await chargingStation.ocppRequestService.requestHandler<
-          AuthorizeRequest,
-          AuthorizeResponse
-        >(chargingStation, RequestCommand.AUTHORIZE, {
-          idTag,
-        })
-      )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
-    );
-  }
-
   private static logPrefix = (
     ocppVersion: OCPPVersion,
     moduleName?: string,
index 02fadb3e24f391dac3c19599451169368a65cfaf..2f21bd2ecf59674ee19acd58972237c7831796fc 100644 (file)
@@ -8,4 +8,9 @@ export { OCPP20RequestService } from './2.0/OCPP20RequestService';
 export { OCPP20ResponseService } from './2.0/OCPP20ResponseService';
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService';
 export { OCPPRequestService } from './OCPPRequestService';
-export { OCPPServiceUtils } from './OCPPServiceUtils';
+export {
+  buildStatusNotificationRequest,
+  getMessageTypeString,
+  isIdTagAuthorized,
+  sendAndSetConnectorStatus,
+} from './OCPPServiceUtils';