- public async sendMessage(messageId: string, commandParams: any, messageType: MessageType = MessageType.CALL_RESULT_MESSAGE, commandName: RequestCommand | IncomingRequestCommand): Promise<any> {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- const chargingStation = this.chargingStation;
- // Send a message through wsConnection
- return new Promise((resolve: (value?: any | PromiseLike<any>) => void, reject: (reason?: any) => void) => {
- let messageToSend: string;
- // Type of message
- switch (messageType) {
- // Request
- case MessageType.CALL_MESSAGE:
- // Build request
- this.chargingStation.requests[messageId] = [responseCallback, rejectCallback, commandParams] as Request;
- messageToSend = JSON.stringify([messageType, messageId, commandName, commandParams]);
- break;
- // Response
- case MessageType.CALL_RESULT_MESSAGE:
- // Build response
- messageToSend = JSON.stringify([messageType, messageId, commandParams]);
- break;
- // Error Message
- case MessageType.CALL_ERROR_MESSAGE:
- // Build Error Message
- messageToSend = JSON.stringify([messageType, messageId, commandParams.code ? commandParams.code : ErrorType.GENERIC_ERROR, commandParams.message ? commandParams.message : '', commandParams.details ? commandParams.details : {}]);
- break;
- }
- // Check if wsConnection opened and charging station registered
- if (this.chargingStation.isWebSocketOpen() && (this.chargingStation.isRegistered() || commandName === RequestCommand.BOOT_NOTIFICATION)) {
- if (this.chargingStation.getEnableStatistics()) {
- this.chargingStation.statistics.addMessage(commandName, messageType);
- }
- // Yes: Send Message
- this.chargingStation.wsConnection.send(messageToSend);
- } else if (commandName !== RequestCommand.BOOT_NOTIFICATION) {
- let dups = false;
- // Handle dups in buffer
- for (const message of this.chargingStation.messageQueue) {
- // Same message
- if (messageToSend === message) {
- dups = true;
- break;
+ public static getInstance<T extends OCPPRequestService>(
+ this: new (ocppResponseService: OCPPResponseService) => T,
+ ocppResponseService: OCPPResponseService,
+ ): T {
+ if (OCPPRequestService.instance === null) {
+ OCPPRequestService.instance = new this(ocppResponseService);
+ }
+ return OCPPRequestService.instance as T;
+ }
+
+ public async sendResponse(
+ chargingStation: ChargingStation,
+ messageId: string,
+ messagePayload: JsonType,
+ commandName: IncomingRequestCommand,
+ ): Promise<ResponseType> {
+ try {
+ // Send response message
+ return await this.internalSendMessage(
+ chargingStation,
+ messageId,
+ messagePayload,
+ MessageType.CALL_RESULT_MESSAGE,
+ commandName,
+ );
+ } catch (error) {
+ handleSendMessageError(chargingStation, commandName, error as Error, {
+ throwError: true,
+ });
+ return null;
+ }
+ }
+
+ public async sendError(
+ chargingStation: ChargingStation,
+ messageId: string,
+ ocppError: OCPPError,
+ commandName: RequestCommand | IncomingRequestCommand,
+ ): Promise<ResponseType> {
+ try {
+ // Send error message
+ return await this.internalSendMessage(
+ chargingStation,
+ messageId,
+ ocppError,
+ MessageType.CALL_ERROR_MESSAGE,
+ commandName,
+ );
+ } catch (error) {
+ handleSendMessageError(chargingStation, commandName, error as Error);
+ return null;
+ }
+ }
+
+ protected async sendMessage(
+ chargingStation: ChargingStation,
+ messageId: string,
+ messagePayload: JsonType,
+ commandName: RequestCommand,
+ params?: RequestParams,
+ ): Promise<ResponseType> {
+ params = {
+ ...defaultRequestParams,
+ ...params,
+ };
+ try {
+ return await this.internalSendMessage(
+ chargingStation,
+ messageId,
+ messagePayload,
+ MessageType.CALL_MESSAGE,
+ commandName,
+ params,
+ );
+ } catch (error) {
+ handleSendMessageError(chargingStation, commandName, error as Error, {
+ throwError: params.throwError,
+ });
+ return null;
+ }
+ }
+
+ private validateRequestPayload<T extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: RequestCommand | IncomingRequestCommand,
+ payload: T,
+ ): boolean {
+ if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
+ return true;
+ }
+ if (this.jsonSchemas.has(commandName as RequestCommand) === false) {
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation`,
+ );
+ return true;
+ }
+ const validate = this.getJsonRequestValidateFunction<T>(commandName as RequestCommand);
+ payload = cloneObject<T>(payload);
+ OCPPServiceUtils.convertDateToISOString<T>(payload);
+ if (validate(payload)) {
+ return true;
+ }
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`,
+ validate.errors,
+ );
+ // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+ throw new OCPPError(
+ OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
+ 'Request PDU is invalid',
+ commandName,
+ JSON.stringify(validate.errors, undefined, 2),
+ );
+ }
+
+ private getJsonRequestValidateFunction<T extends JsonType>(commandName: RequestCommand) {
+ if (this.jsonValidateFunctions.has(commandName) === false) {
+ this.jsonValidateFunctions.set(
+ commandName,
+ this.ajv.compile<T>(this.jsonSchemas.get(commandName)!).bind(this),
+ );
+ }
+ return this.jsonValidateFunctions.get(commandName)!;
+ }
+
+ private validateIncomingRequestResponsePayload<T extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: RequestCommand | IncomingRequestCommand,
+ payload: T,
+ ): boolean {
+ if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
+ return true;
+ }
+ if (
+ this.ocppResponseService.jsonIncomingRequestResponseSchemas.has(
+ commandName as IncomingRequestCommand,
+ ) === false
+ ) {
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema found for command '${commandName}' PDU validation`,
+ );
+ return true;
+ }
+ const validate = this.getJsonRequestResponseValidateFunction<T>(
+ commandName as IncomingRequestCommand,
+ );
+ payload = cloneObject<T>(payload);
+ OCPPServiceUtils.convertDateToISOString<T>(payload);
+ if (validate(payload)) {
+ return true;
+ }
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' reponse PDU is invalid: %j`,
+ validate.errors,
+ );
+ // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+ throw new OCPPError(
+ OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
+ 'Response PDU is invalid',
+ commandName,
+ JSON.stringify(validate.errors, undefined, 2),
+ );
+ }
+
+ private getJsonRequestResponseValidateFunction<T extends JsonType>(
+ commandName: IncomingRequestCommand,
+ ) {
+ if (
+ this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.has(commandName) ===
+ false
+ ) {
+ this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.set(
+ commandName,
+ this.ajv
+ .compile<T>(this.ocppResponseService.jsonIncomingRequestResponseSchemas.get(commandName)!)
+ .bind(this),
+ );
+ }
+ return this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.get(commandName)!;
+ }
+
+ private async internalSendMessage(
+ chargingStation: ChargingStation,
+ messageId: string,
+ messagePayload: JsonType | OCPPError,
+ messageType: MessageType,
+ commandName: RequestCommand | IncomingRequestCommand,
+ params?: RequestParams,
+ ): Promise<ResponseType> {
+ params = {
+ ...defaultRequestParams,
+ ...params,
+ };
+ if (
+ (chargingStation.inUnknownState() === true &&
+ commandName === RequestCommand.BOOT_NOTIFICATION) ||
+ (chargingStation.stationInfo?.ocppStrictCompliance === false &&
+ chargingStation.inUnknownState() === true) ||
+ chargingStation.inAcceptedState() === true ||
+ (chargingStation.inPendingState() === true &&
+ (params.triggerMessage === true || messageType === MessageType.CALL_RESULT_MESSAGE))
+ ) {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const self = this;
+ // Send a message through wsConnection
+ return await new Promise<ResponseType>((resolve, reject) => {
+ /**
+ * Function that will receive the request's response
+ *
+ * @param payload -
+ * @param requestPayload -
+ */
+ const responseCallback = (payload: JsonType, requestPayload: JsonType): void => {
+ if (chargingStation.stationInfo?.enableStatistics === true) {
+ chargingStation.performanceStatistics?.addRequestStatistic(
+ commandName,
+ MessageType.CALL_RESULT_MESSAGE,
+ );