+ protected constructor() {
+ // This is intentional
+ }
+
+ public static ajvErrorsToErrorType(errors: ErrorObject[] | null | undefined): ErrorType {
+ if (isNotEmptyArray(errors) === true) {
+ for (const error of errors as DefinedError[]) {
+ switch (error.keyword) {
+ case 'type':
+ return ErrorType.TYPE_CONSTRAINT_VIOLATION;
+ case 'dependencies':
+ case 'required':
+ return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
+ case 'pattern':
+ case 'format':
+ return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
+ }
+ }
+ }
+ 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,
+ ): boolean {
+ const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
+ if (
+ isRequestCommand === true &&
+ !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
+ ) {
+ return true;
+ } else if (
+ isRequestCommand === true &&
+ chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command]
+ ) {
+ return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command];
+ }
+ logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
+ return false;
+ }
+
+ public static isIncomingRequestCommandSupported(
+ chargingStation: ChargingStation,
+ command: IncomingRequestCommand,
+ ): boolean {
+ const isIncomingRequestCommand =
+ Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
+ if (
+ isIncomingRequestCommand === true &&
+ !chargingStation.stationInfo?.commandsSupport?.incomingCommands
+ ) {
+ return true;
+ } else if (
+ isIncomingRequestCommand === true &&
+ chargingStation.stationInfo?.commandsSupport?.incomingCommands?.[command]
+ ) {
+ return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command];
+ }
+ logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
+ return false;
+ }
+
+ public static isMessageTriggerSupported(
+ chargingStation: ChargingStation,
+ messageTrigger: MessageTrigger,
+ ): boolean {
+ const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger);
+ if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) {
+ return true;
+ } else if (
+ isMessageTrigger === true &&
+ chargingStation.stationInfo?.messageTriggerSupport?.[messageTrigger]
+ ) {
+ return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger];
+ }
+ logger.error(
+ `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`,
+ );
+ return false;
+ }
+
+ public static isConnectorIdValid(
+ chargingStation: ChargingStation,
+ ocppCommand: IncomingRequestCommand,
+ connectorId: number,
+ ): boolean {
+ if (connectorId < 0) {
+ logger.error(
+ `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`,
+ );
+ return false;
+ }
+ return true;
+ }
+
+ public static convertDateToISOString<T extends JsonType>(obj: T): void {
+ for (const key in obj) {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ if (isDate(obj![key])) {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ (obj![key] as string) = (obj![key] as Date).toISOString();
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ } else if (obj![key] !== null && typeof obj![key] === 'object') {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ OCPPServiceUtils.convertDateToISOString<T>(obj![key] as T);
+ }
+ }
+ }
+
+ 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) {
+ chargingStation.startHeartbeat();
+ } else if (chargingStation.getHeartbeatInterval() !== interval) {
+ chargingStation.restartHeartbeat();
+ }
+ }
+
+ 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`,
+ );