-import type { DefinedError, ErrorObject } from 'ajv';
-
-import BaseError from '../../exception/BaseError';
-import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
-import { StandardParametersKey } from '../../types/ocpp/Configuration';
-import { ErrorType } from '../../types/ocpp/ErrorType';
-import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
-import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../types/ocpp/Requests';
-import Constants from '../../utils/Constants';
-import logger from '../../utils/Logger';
-import Utils from '../../utils/Utils';
-import type ChargingStation from '../ChargingStation';
-import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
+import fs from 'node:fs';
+
+import type { DefinedError, ErrorObject, JSONSchemaType } from 'ajv';
+
+import { OCPP16Constants, OCPP20Constants } from './internal';
+import { type ChargingStation, ChargingStationConfigurationUtils } from '../../charging-station';
+import { BaseError } from '../../exception';
+import {
+ ChargePointErrorCode,
+ type ConnectorStatusEnum,
+ ErrorType,
+ FileType,
+ IncomingRequestCommand,
+ type JsonObject,
+ type JsonType,
+ MessageTrigger,
+ MessageType,
+ MeterValueMeasurand,
+ type MeterValuePhase,
+ type OCPP16StatusNotificationRequest,
+ type OCPP20StatusNotificationRequest,
+ OCPPVersion,
+ RequestCommand,
+ type SampledValueTemplate,
+ StandardParametersKey,
+ type StatusNotificationRequest,
+ type StatusNotificationResponse,
+} from '../../types';
+import { Constants, FileUtils, Utils, logger } from '../../utils';
export class OCPPServiceUtils {
protected constructor() {
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).includes(command);
+ const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command);
if (
isRequestCommand === true &&
!chargingStation.stationInfo?.commandsSupport?.outgoingCommands
chargingStation: ChargingStation,
command: IncomingRequestCommand
): boolean {
- const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
+ const isIncomingRequestCommand =
+ Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command);
if (
isIncomingRequestCommand === true &&
!chargingStation.stationInfo?.commandsSupport?.incomingCommands
): boolean {
if (connectorId < 0) {
logger.error(
- `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector Id ${connectorId}`
+ `${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) {
+ if (obj[key] instanceof Date) {
+ (obj as JsonObject)[key] = (obj[key] as Date).toISOString();
+ } else if (obj[key] !== null && typeof obj[key] === 'object') {
+ OCPPServiceUtils.convertDateToISOString<T>(obj[key] as T);
+ }
+ }
+ }
+
+ public static buildStatusNotificationRequest(
+ chargingStation: ChargingStation,
+ connectorId: number,
+ status: ConnectorStatusEnum
+ ): StatusNotificationRequest {
+ switch (chargingStation.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16) {
+ 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: connectorId,
+ } 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
+ ) {
+ OCPPServiceUtils.checkConnectorStatusTransition(chargingStation, connectorId, status);
+ await chargingStation.ocppRequestService.requestHandler<
+ StatusNotificationRequest,
+ StatusNotificationResponse
+ >(
+ chargingStation,
+ RequestCommand.STATUS_NOTIFICATION,
+ OCPPServiceUtils.buildStatusNotificationRequest(chargingStation, connectorId, status)
+ );
+ chargingStation.getConnectorStatus(connectorId).status = status;
+ }
+
+ 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
+ ) {
+ transitionAllowed = true;
+ } else if (
+ 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
+ ) {
+ transitionAllowed = true;
+ } else if (
+ 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 ${connectorId} status transition from '${
+ chargingStation.getConnectorStatus(connectorId).status
+ }' to '${status}' is not allowed`
+ );
+ }
+ return transitionAllowed;
+ }
+
+ protected static parseJsonSchemaFile<T extends JsonType>(
+ filePath: string,
+ ocppVersion: OCPPVersion,
+ moduleName?: string,
+ methodName?: string
+ ): JSONSchemaType<T> {
+ try {
+ return JSON.parse(fs.readFileSync(filePath, 'utf8')) as JSONSchemaType<T>;
+ } catch (error) {
+ FileUtils.handleFileException(
+ filePath,
+ FileType.JsonSchema,
+ error as NodeJS.ErrnoException,
+ OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName),
+ { throwError: false }
+ );
+ }
+ }
+
protected static getSampledValueTemplate(
chargingStation: ChargingStation,
connectorId: number,
ChargingStationConfigurationUtils.getConfigurationKey(
chargingStation,
StandardParametersKey.MeterValuesSampledData
- )?.value.includes(measurand) === false
+ )?.value?.includes(measurand) === false
) {
logger.debug(
`${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
return;
}
const sampledValueTemplates: SampledValueTemplate[] =
- chargingStation.getConnectorStatus(connectorId).MeterValues;
+ chargingStation.getConnectorStatus(connectorId)?.MeterValues;
for (
let index = 0;
- Utils.isEmptyArray(sampledValueTemplates) === false && index < sampledValueTemplates.length;
+ Utils.isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length;
index++
) {
if (
ChargingStationConfigurationUtils.getConfigurationKey(
chargingStation,
StandardParametersKey.MeterValuesSampledData
- )?.value.includes(measurand) === true
+ )?.value?.includes(measurand) === true
) {
return sampledValueTemplates[index];
} else if (
ChargingStationConfigurationUtils.getConfigurationKey(
chargingStation,
StandardParametersKey.MeterValuesSampledData
- )?.value.includes(measurand) === true
+ )?.value?.includes(measurand) === true
) {
return sampledValueTemplates[index];
} else if (
? Math.min(numberValue * options.unitMultiplier, limit)
: numberValue * options.unitMultiplier;
}
+
+ private static logPrefix = (
+ ocppVersion: OCPPVersion,
+ moduleName?: string,
+ methodName?: string
+ ): string => {
+ const logMsg =
+ Utils.isNotEmptyString(moduleName) && Utils.isNotEmptyString(methodName)
+ ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
+ : ` OCPP ${ocppVersion} |`;
+ return Utils.logPrefix(logMsg);
+ };
}